Skills react-project-standard
React + TypeScript 项目的完整工程规范,涵盖项目结构、组件设计、Hooks、路由、状态管理、API 层、错误处理、测试和性能优化。当用户在 React 项目中创建、修改组件或模块,涉及架构设计、代码编写时自动激活。
install
source · Clone the upstream repo
git clone https://github.com/openclaw/skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/openclaw/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/bovinphang/react-project-standard" ~/.claude/skills/openclaw-skills-react-project-standard && rm -rf "$T"
OpenClaw · Install into ~/.openclaw/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/openclaw/skills "$T" && mkdir -p ~/.openclaw/skills && cp -r "$T/skills/bovinphang/react-project-standard" ~/.openclaw/skills/openclaw-skills-react-project-standard && rm -rf "$T"
manifest:
skills/bovinphang/react-project-standard/SKILL.mdsource content
React 项目规范
适用于使用 React + TypeScript 的仓库。
项目结构
以下为中大型 React 项目的业界最佳实践结构,按项目实际情况裁剪:
src/ ├── app/ # 应用入口与全局配置 │ ├── App.tsx # 根组件(Provider 组合) │ ├── routes.tsx # 路由配置 │ └── providers.tsx # 全局 Provider 组装 │ ├── pages/ # 页面组件(与路由一一对应) │ ├── Dashboard/ │ │ ├── DashboardPage.tsx │ │ ├── components/ # 页面私有组件 │ │ └── hooks/ # 页面私有 hooks │ ├── UserList/ │ └── Settings/ │ ├── layouts/ # 布局组件 │ ├── MainLayout.tsx # 主布局(侧边栏 + 顶栏 + 内容区) │ ├── AuthLayout.tsx # 登录/注册页布局 │ └── BlankLayout.tsx # 空白布局(错误页等) │ ├── features/ # 功能模块(按业务领域划分) │ ├── auth/ │ │ ├── components/ # 模块组件 │ │ ├── hooks/ # 模块 hooks │ │ ├── api.ts # 模块 API 调用 │ │ ├── types.ts # 模块类型定义 │ │ ├── constants.ts # 模块常量 │ │ └── index.ts # 模块公开导出 │ └── order/ │ ├── components/ # 全局共享 UI 组件 │ ├── Button/ │ │ ├── Button.tsx │ │ ├── Button.module.css │ │ └── __tests__/ │ ├── Modal/ │ ├── Form/ │ └── ErrorBoundary/ │ ├── hooks/ # 全局共享 hooks │ ├── useAuth.ts │ ├── useDebounce.ts │ └── useMediaQuery.ts │ ├── services/ # API 基础层 │ ├── request.ts # Axios/fetch 实例与拦截器 │ └── endpoints/ # API 端点定义(如按领域拆分) │ ├── stores/ # 全局状态管理 │ ├── authStore.ts │ └── uiStore.ts │ ├── locales/ # 国际化语言包 │ ├── zh-CN.json # 中文 │ ├── en-US.json # 英文 │ └── index.ts # i18n 实例初始化(i18next / react-intl) │ ├── assets/ # 静态资源 │ ├── images/ # 图片(PNG、JPG、WebP) │ ├── icons/ # SVG 图标 │ └── fonts/ # 自定义字体 │ ├── config/ # 应用配置 │ ├── env.ts # 环境变量类型化封装 │ └── features.ts # Feature Flags 管理 │ ├── types/ # 全局共享类型 │ ├── api.ts # API 响应/请求通用类型 │ ├── models.ts # 业务实体类型 │ └── global.d.ts # 全局类型扩展(图片模块声明等) │ ├── utils/ # 纯工具函数 │ ├── format.ts # 日期、数字、货币格式化 │ ├── validators.ts # 表单校验规则 │ └── storage.ts # LocalStorage / SessionStorage 封装 │ ├── styles/ # 全局样式与主题 │ ├── global.css # 全局基础样式(reset / normalize) │ ├── variables.css # CSS 变量(颜色、间距、字号) │ ├── breakpoints.ts # 响应式断点常量 │ └── themes/ # 主题定义 │ ├── light.css # 亮色主题变量 │ ├── dark.css # 暗色主题变量 │ └── index.ts # 主题切换逻辑 │ └── constants/ # 全局常量 ├── routes.ts # 路由路径常量 └── config.ts # 业务常量(分页大小、超时时间等)
关键原则
做路由映射和布局组合,不放业务逻辑pages/
定义页面骨架(侧边栏、顶栏、面包屑),由路由配置引用layouts/
按业务领域划分,模块内自包含(components + hooks + api + types)features/
仅放无业务耦合的通用组件,可跨项目复用components/
仅放通用逻辑(防抖、媒体查询等),业务 hooks 放到对应 feature 中hooks/
存放语言包 JSON 文件,组件中使用locales/
而非硬编码文案t('key')
存放静态资源,图标优先使用 SVG,图片优先使用 WebP/AVIFassets/
封装环境变量和 Feature Flags,禁止组件中直接读取config/import.meta.env
通过 CSS 变量实现主题切换,组件中引用变量而非硬编码颜色styles/themes/- 每个模块通过
管控公开 API,避免深层路径导入index.ts
组件设计规范
- 使用函数组件和 TypeScript
- 保持组件职责单一、可组合
- 将可复用逻辑提取到 hooks
- 在合适场景优先使用受控组件 API
- props 定义清晰且类型明确
- 优先复用现有设计系统组件
- 保持可访问性与键盘交互
- 避免过深的 JSX 嵌套和重复分支
- 对可推导的值不要额外存 state
组件文件结构
ComponentName/ ├── ComponentName.tsx ├── ComponentName.types.ts # 类型定义(如需独立) ├── ComponentName.module.css # 样式(如需) ├── hooks/ │ └── useComponentLogic.ts ├── components/ │ └── SubComponent.tsx └── __tests__/ └── ComponentName.spec.tsx
组件分层
页面组件 (Pages) → 路由映射、布局组合 └── 容器组件 (Containers) → 数据获取、状态编排 └── 业务组件 (Features) → 领域逻辑展示 └── 通用组件 (UI) → 纯展示,无业务耦合
TypeScript 规范
- Props interface 命名:
ComponentNameProps - 事件处理函数:
前缀(如handle
)handleClick - 回调 Props:
前缀(如on
、onChange
)onSubmit - 禁止使用
,优先使用any
或精确类型unknown - 泛型组件使用
约束,保持类型推导<T>
interface TableProps<T extends Record<string, unknown>> { data: T[]; columns: ColumnDef<T>[]; onRowClick?: (row: T) => void; }
Hooks 规范
自定义 Hook 设计
- 以
前缀命名use - 返回值使用对象(而非数组),方便按需解构
- 状态和行为封装在一起,组件只消费结果
- Hook 内部处理 loading / error / data 三态
function useUserList(params: QueryParams) { const [data, setData] = useState<User[]>([]); const [loading, setLoading] = useState(false); const [error, setError] = useState<Error | null>(null); const fetch = useCallback(async () => { setLoading(true); setError(null); try { const res = await getUserList(params); setData(res.list); } catch (e) { setError(e as Error); } finally { setLoading(false); } }, [params]); useEffect(() => { fetch(); }, [fetch]); return { data, loading, error, refetch: fetch }; }
Hook 使用原则
必须有正确的依赖数组和清理函数useEffect
/useMemo
仅在子组件使用 memo 或依赖稳定性重要时使用useCallback- 不要在条件分支或循环中调用 hooks
- 数据请求场景优先使用 React Query / SWR 等库(如项目已引入)
路由规范
路由组织
// app/routes.tsx const routes: RouteObject[] = [ { path: '/', element: <MainLayout />, children: [ { index: true, element: <DashboardPage /> }, { path: 'users', element: <UserListPage /> }, { path: 'users/:id', element: <UserDetailPage /> }, { path: 'settings', element: <SettingsPage /> }, ], }, { path: '/login', element: <LoginPage /> }, { path: '*', element: <NotFoundPage /> }, ];
路由原则
- 路由配置集中管理,路径常量化
- 页面组件使用
+lazy
按需加载Suspense - 权限路由使用 guard 组件包裹,而非在每个页面内判断
- URL 参数(分页、筛选、排序)与路由状态同步
const UserListPage = lazy(() => import('@/pages/UserList/UserListPage')); function ProtectedRoute({ children }: { children: React.ReactNode }) { const { isAuthenticated } = useAuth(); if (!isAuthenticated) return <Navigate to="/login" replace />; return <>{children}</>; }
状态管理
| 状态类型 | 推荐方案 |
|---|---|
| 组件内临时 UI 状态 | / |
| 跨组件共享业务状态 | 全局 store(Zustand / Redux Toolkit) |
| 服务端数据缓存 | React Query / SWR |
| URL 驱动状态 | 路由参数 / |
| 表单状态 | React Hook Form / Formik |
核心原则
- 就近管理:状态放在需要它的最近公共祖先
- 单一数据源:同一份数据只在一个地方维护
- 派生优于同步:可计算的值直接在渲染中计算
- 服务端数据用请求库管理,而非手动存入 store
API 层规范
请求实例
// services/request.ts const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 10000, }); request.interceptors.response.use( (res) => res.data, (error) => { if (error.response?.status === 401) { redirectToLogin(); } return Promise.reject(normalizeError(error)); }, );
API 函数
// features/user/api.ts export function getUserList(params: UserQueryParams): Promise<PageResult<User>> { return request.get('/users', { params }); } export function updateUser(id: string, data: UpdateUserDTO): Promise<User> { return request.put(`/users/${id}`, data); }
- API 函数按 feature 组织,而非全部堆在一个文件
- 请求参数和响应都有类型约束
- 拦截器统一处理认证、错误格式化、loading 提示
错误处理
Error Boundary
每个功能模块应有 Error Boundary 包裹,避免局部错误导致全页白屏:
<ErrorBoundary fallback={<ModuleErrorFallback />}> <UserDashboard /> </ErrorBoundary>
异步错误
- 数据请求失败必须有用户可见的错误提示
- 提供重试机制
- 不要吞掉错误(空 catch 块)
Suspense 与懒加载
const HeavyChart = lazy(() => import('./components/HeavyChart')); function Dashboard() { return ( <Suspense fallback={<ChartSkeleton />}> <HeavyChart data={chartData} /> </Suspense> ); }
- 路由级组件使用
+lazySuspense - 重型第三方组件(图表、编辑器、地图)按需加载
使用 Skeleton 而非简单 spinnerfallback
样式规范
- 与项目现有样式体系保持一致(Tailwind / CSS Modules / styled-components 等)
- 复杂动画或特殊样式使用 CSS Modules 或项目约定方案
- 禁止内联样式(除动态值外)
- 响应式处理需与项目断点约定一致
- 主题/全局变量通过 CSS 变量或 Token 管理
测试规范
必须测试
- 核心交互行为(点击、输入、提交)
- 条件渲染(loading / error / empty / data)
- 关键 hooks 的返回值和副作用
- API 调用 mock 与集成测试
测试风格
describe('UserForm', () => { it('should submit with valid data', async () => { const onSubmit = vi.fn(); render(<UserForm onSubmit={onSubmit} />); await userEvent.type(screen.getByLabelText('用户名'), 'test'); await userEvent.click(screen.getByRole('button', { name: '提交' })); expect(onSubmit).toHaveBeenCalledWith({ username: 'test' }); }); it('should show error on invalid input', async () => { render(<UserForm onSubmit={vi.fn()} />); await userEvent.click(screen.getByRole('button', { name: '提交' })); expect(screen.getByText('用户名不能为空')).toBeInTheDocument(); }); });
- 使用
/screen.getByRole
而非getByLabelTextgetByTestId - 测试用户行为,而非实现细节
- 避免测试组件内部 state
性能
- 合理使用
、React.memo
、useMemouseCallback - 列表渲染必须有稳定的
key - 避免在渲染函数内创建新对象/数组/函数
- 大列表使用虚拟滚动(react-window / react-virtuoso)
- 避免在顶层 context 中放置高频变化的值
- 路由级 code splitting,重型组件 lazy load
反模式
- 用 prop drilling 传递过多层级而不考虑组合方案
- 对局部问题过度使用 context
- 在确实有影响的情况下仍使用不稳定回调
- 在同一个文件里同时混合数据请求、视图渲染和命令式 DOM 逻辑
- 无充分理由引入第二套样式方案
- 在 useEffect 中做可以在事件处理函数中完成的事
- 用
+useEffect
模拟 computed(应直接在渲染中计算)setState - 将所有状态推入全局 store,而非就近管理
- 在
中放业务耦合组件components/ - 直接从 feature 内部深层路径导入,绕过
index.ts
输出检查清单
- 文件结构与项目约定一致(pages / features / components 分离)
- Props 类型完整且明确
- 可复用逻辑已提取到 hooks
- Error Boundary 已包裹关键模块
- Loading / Error / Empty 状态均已处理
- 路由组件使用 lazy 加载
- 状态管理方案合理(就近原则)
- API 调用有类型约束和统一错误处理
- 样式接入方式与仓库保持一致
- 关键行为有测试覆盖