
1. 为什么需要ReactTypeScript开发规范在大型前端项目中随着团队规模扩大和功能复杂度提升代码一致性、可维护性和类型安全变得至关重要。ReactTypeScript的组合已经成为现代前端开发的主流选择但如果没有统一的开发规范很容易出现以下问题组件props类型定义混乱导致运行时类型错误状态管理方式不一致难以追踪数据流代码风格差异大增加review和维护成本缺乏最佳实践指导新人上手困难我们团队经过多个中大型项目实践总结出一套完整的ReactTypeScript开发规范覆盖从组件设计到状态管理的各个环节。2. 组件设计与实现规范2.1 组件类型定义在TypeScript中我们推荐使用interface定义组件props类型interface ButtonProps { size?: small | medium | large; variant?: primary | secondary | text; disabled?: boolean; onClick?: (event: React.MouseEvent) void; }注意避免使用type定义props因为interface支持声明合并在扩展类型时更灵活2.2 函数组件实现对于函数组件我们采用以下标准写法const Button: React.FCButtonProps ({ size medium, variant primary, disabled false, onClick, children }) { // 组件逻辑... return ( button className{btn-${size} btn-${variant}} disabled{disabled} onClick{onClick} {children} /button ); };关键规范点使用React.FC泛型明确组件类型为可选props提供默认值解构props到单独行保持可读性事件处理函数使用标准React事件类型2.3 类组件实现虽然函数组件已成为主流但在某些场景下仍需使用类组件class Counter extends React.ComponentCounterProps, CounterState { // 明确声明props和state类型 state: CounterState { count: 0 }; // 使用箭头函数绑定this handleIncrement () { this.setState(prev ({ count: prev.count 1 })); }; render() { return ( div span{this.state.count}/span button onClick{this.handleIncrement}/button /div ); } }3. 状态管理规范3.1 本地状态管理对于组件内部状态我们遵循以下原则简单状态使用useStateconst [visible, setVisible] useStateboolean(false);复杂状态使用useReducertype State { count: number }; type Action { type: increment } | { type: decrement }; const reducer (state: State, action: Action) { switch (action.type) { case increment: return { count: state.count 1 }; case decrement: return { count: state.count - 1 }; default: return state; } }; const [state, dispatch] useReducer(reducer, { count: 0 });3.2 全局状态管理对于跨组件状态共享我们推荐使用Redux ToolkitTypeScript// store.ts import { configureStore } from reduxjs/toolkit; const store configureStore({ reducer: { counter: counterReducer, user: userReducer } }); // counterSlice.ts import { createSlice, PayloadAction } from reduxjs/toolkit; interface CounterState { value: number; } const initialState: CounterState { value: 0, }; export const counterSlice createSlice({ name: counter, initialState, reducers: { increment: (state) { state.value 1; }, decrement: (state) { state.value - 1; }, incrementByAmount: (state, action: PayloadActionnumber) { state.value action.payload; }, }, });4. 类型定义最佳实践4.1 通用类型提取将重复使用的类型提取到单独的类型文件中// types/user.ts export interface User { id: string; name: string; email: string; role: admin | user | guest; } // types/api.ts export interface ApiResponseT { data: T; error?: string; status: number; }4.2 实用工具类型利用TypeScript内置工具类型简化代码// 从已有类型中排除某些属性 type ButtonWithoutSize OmitButtonProps, size; // 只读版本的类型 type ReadonlyUser ReadonlyUser; // 可选属性转为必选 type RequiredUser RequiredUser;5. 代码风格与组织规范5.1 文件结构推荐按功能组织代码src/ components/ Button/ index.tsx // 组件入口 types.ts // 类型定义 styles.module.css // 样式 __tests__/ // 测试文件 features/ user/ userSlice.ts // Redux slice userApi.ts // API调用 hooks/ useAuth.ts utils/ helpers.ts5.2 命名约定组件文件使用PascalCaseUserProfile.tsx自定义hook以use开头useWindowSize类型定义使用PascalCaseUserProfileProps常量使用UPPER_CASEMAX_ITEMS 106. 测试规范6.1 单元测试使用React Testing Library编写测试import { render, screen, fireEvent } from testing-library/react; import Button from ./Button; describe(Button, () { it(should call onClick when clicked, () { const handleClick jest.fn(); render(Button onClick{handleClick}Click me/Button); fireEvent.click(screen.getByText(Click me)); expect(handleClick).toHaveBeenCalledTimes(1); }); });6.2 类型测试使用ts-expect-error验证类型错误// 确保size只能接受特定值 Button sizesmall /; // ✅ Button sizeextra-large /; // ts-expect-error ❌7. 常见问题与解决方案7.1 事件类型处理常见错误是直接使用any作为事件类型// ❌ 避免这样 const handleChange (e: any) {...} // ✅ 正确做法 const handleChange (e: React.ChangeEventHTMLInputElement) {...}7.2 泛型组件当组件需要支持多种类型时interface ListPropsT { items: T[]; renderItem: (item: T) React.ReactNode; } function ListT({ items, renderItem }: ListPropsT) { return div{items.map(renderItem)}/div; } // 使用 Listnumber items{[1, 2, 3]} renderItem{(item) span{item}/span} /7.3 第三方库类型扩展当需要扩展第三方库类型时// 扩展styled-components主题类型 import styled-components; declare module styled-components { export interface DefaultTheme { colors: { primary: string; secondary: string; }; } }8. 性能优化建议8.1 减少重新渲染使用React.memo优化组件const MemoButton React.memo(Button);8.2 正确使用useCallback避免不必要的函数重新创建// ❌ 每次渲染都会创建新函数 const handleClick () {...} // ✅ 使用useCallback缓存函数 const handleClick useCallback(() { // 函数逻辑 }, [dependencies]);8.3 按需导入使用动态导入减少bundle大小const HeavyComponent React.lazy(() import(./HeavyComponent)); function MyComponent() { return ( React.Suspense fallback{divLoading.../div} HeavyComponent / /React.Suspense ); }9. 代码提交与Review规范9.1 Commit Message格式使用Angular Commit Message规范feat(button): add disabled state support Add new disabled prop to Button component and update styles accordingly. BREAKING CHANGE: Button now requires explicit disabled state9.2 Code Review要点类型定义是否完整准确组件props是否有默认值状态管理是否符合规范代码结构是否清晰测试覆盖率是否足够10. 工具与配置推荐10.1 ESLint配置推荐配置{ extends: [ eslint:recommended, plugin:typescript-eslint/recommended, plugin:react/recommended, plugin:react-hooks/recommended ], rules: { typescript-eslint/explicit-function-return-type: off, react/prop-types: off, react-hooks/rules-of-hooks: error, react-hooks/exhaustive-deps: warn } }10.2 Prettier配置保持代码风格一致{ printWidth: 100, tabWidth: 2, useTabs: false, semi: true, singleQuote: true, trailingComma: all, bracketSpacing: true, jsxBracketSameLine: false, arrowParens: always }10.3 VSCode插件推荐ESLintPrettierTypeScript Vue PluginReact RefactorCode Spell Checker11. 项目初始化模板我们维护了一个标准的项目模板npx create-react-app my-app --template typescript cd my-app npm install reduxjs/toolkit react-redux types/react-redux npm install -D eslint prettier eslint-config-prettier typescript-eslint/eslint-plugin提示初始化后立即配置ESLint和Prettier确保团队统一代码风格12. 持续集成配置示例GitHub Actions配置name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv2 - uses: actions/setup-nodev2 with: node-version: 14 - run: npm ci - run: npm run lint - run: npm test - run: npm run build13. 文档规范13.1 组件文档使用Storybook记录组件import { Meta, Story } from storybook/react; import Button, { ButtonProps } from ./Button; export default { title: Components/Button, component: Button, } as Meta; const Template: StoryButtonProps (args) Button {...args} /; export const Primary Template.bind({}); Primary.args { variant: primary, children: Primary Button, };13.2 API文档使用TypeDoc生成类型文档npm install -D typedoc npx typedoc --out docs src14. 迁移指南14.1 从JavaScript迁移将.js文件重命名为.tsx逐步添加类型定义修复类型错误启用严格模式14.2 从PropTypes迁移// 之前 Button.propTypes { size: PropTypes.oneOf([small, medium, large]), onClick: PropTypes.func, }; // 之后 interface ButtonProps { size?: small | medium | large; onClick?: (event: React.MouseEvent) void; }15. 高级模式15.1 条件渲染类型根据props动态调整组件类型interface BaseProps { variant: text | outlined; } interface TextProps extends BaseProps { variant: text; underline?: boolean; } interface OutlinedProps extends BaseProps { variant: outlined; borderWidth?: number; } type ButtonProps TextProps | OutlinedProps; function Button(props: ButtonProps) { if (props.variant text) { // 这里props自动推断为TextProps return button className{props.underline ? underline : } /; } else { // 这里props自动推断为OutlinedProps return button style{{ borderWidth: props.borderWidth }} /; } }15.2 高阶组件类型安全的高阶组件实现function withLoadingT extends object( Component: React.ComponentTypeT ): React.FCT { loading?: boolean } { return ({ loading, ...props }) { if (loading) return divLoading.../div; return Component {...props as T} /; }; }16. 实战案例解析16.1 表单处理类型安全的表单组件interface FormValues { username: string; password: string; remember: boolean; } const LoginForm () { const { register, handleSubmit } useFormFormValues(); const onSubmit (data: FormValues) { console.log(data); }; return ( form onSubmit{handleSubmit(onSubmit)} input {...register(username)} / input {...register(password)} typepassword / input {...register(remember)} typecheckbox / button typesubmitLogin/button /form ); };16.2 数据获取Hook类型安全的API Hookfunction useApiT(url: string) { const [data, setData] useStateT | null(null); const [loading, setLoading] useState(true); const [error, setError] useStateError | null(null); useEffect(() { fetch(url) .then(res res.json() as PromiseT) .then(data { setData(data); setLoading(false); }) .catch(err { setError(err); setLoading(false); }); }, [url]); return { data, loading, error }; } // 使用 const { data, loading } useApiUser[](/api/users);17. 错误处理模式17.1 类型保护使用类型守卫处理未知类型function isApiError(error: unknown): error is { message: string } { return typeof error object error ! null message in error; } try { // some code } catch (error) { if (isApiError(error)) { console.error(error.message); } else { console.error(Unknown error); } }17.2 错误边界类型安全的错误边界组件interface ErrorBoundaryProps { fallback: React.ReactNode; children: React.ReactNode; } interface ErrorBoundaryState { hasError: boolean; } class ErrorBoundary extends React.ComponentErrorBoundaryProps, ErrorBoundaryState { state { hasError: false }; static getDerivedStateFromError() { return { hasError: true }; } componentDidCatch(error: Error, info: React.ErrorInfo) { console.error(error, info); } render() { if (this.state.hasError) { return this.props.fallback; } return this.props.children; } }18. 国际化方案类型安全的i18n实现interface Translations { [key: string]: string | Translations; } const resources { en: { greeting: Hello, button: { submit: Submit, cancel: Cancel } }, zh: { greeting: 你好, button: { submit: 提交, cancel: 取消 } } }; function useTranslation(lang: keyof typeof resources) { return function t(key: string): string { const keys key.split(.); let result: any resources[lang]; for (const k of keys) { result result[k]; if (!result) return key; } return result; }; } // 使用 const t useTranslation(en); t(greeting); // Hello t(button.submit); // Submit19. 主题与样式类型安全的主题提供interface Theme { colors: { primary: string; secondary: string; background: string; }; spacing: (factor: number) string; } const defaultTheme: Theme { colors: { primary: #007bff, secondary: #6c757d, background: #ffffff }, spacing: (factor) ${8 * factor}px }; const ThemeContext React.createContextTheme(defaultTheme); function useTheme() { return useContext(ThemeContext); } // 在组件中使用 function ThemedButton() { const theme useTheme(); return ( button style{{ backgroundColor: theme.colors.primary, margin: theme.spacing(1) }} Click me /button ); }20. 性能监控类型安全的性能指标收集interface PerformanceMetrics { componentName: string; mountTime: number; updateTime: number; renderCount: number; } const metricsContext React.createContext{ report: (metrics: PerformanceMetrics) void; }({ report: () {} }); function usePerformance() { return useContext(metricsContext); } function withMetricsT extends object( Component: React.ComponentTypeT, componentName: string ) { return (props: T) { const { report } usePerformance(); const mountStart useRef(performance.now()); const renderCount useRef(0); useEffect(() { const mountTime performance.now() - mountStart.current; report({ componentName, mountTime, updateTime: 0, renderCount: renderCount.current }); }, []); renderCount.current 1; return Component {...props} /; }; }