ia-react-frontend

基于 React 的架构模式、TypeScript、Next.js 及 Hooks,适用于组件结构、状态管理、路由配置、测试等场景。

已扫描
适合谁
前端开发工程师、技术负责人或架构师
不适合谁
初学者快速上手项目、非前端开发人员
国内可用性
需网络配置。可能需要网络配置或第三方服务可访问。
安装难度
新手友好(★☆☆)。基于终端操作、依赖、API Key 和本地环境要求的初步判断。

安装与下载

openclaw skills install @iliaal/compound-eng-react-frontend

Skill 说明

命令、参数、文件名以原文为准

React 前端

实施前请验证:对于 App Router 模式、React 19 API 或版本特定行为,请在编写代码前查阅最新文档(如有 Context7 query-docs,否则通过网络搜索访问框架官方文档)。训练数据可能滞后于最新发布版本。

组件 TypeScript

  • 使用 ComponentPropsWithoutRef<'button'> 扩展原生元素,并通过交叉类型添加自定义属性
  • 子节点使用 React.ReactNode,单个元素使用 React.ReactElement,渲染函数使用 (data: T) => ReactNode
  • 使用区分联合类型(discriminated unions)表示变体属性——TypeScript 在分支中可自动缩小类型
  • 泛型组件:<T> 配合 keyof T 表示列键,T extends { id: string } 用于约束
  • 事件类型:React.MouseEvent<HTMLButtonElement>FormEvent<HTMLFormElement>ChangeEvent<HTMLInputElement>
  • 使用 as const 标记自定义 Hook 返回的元组
  • useRef<HTMLInputElement>(null) 用于 DOM 引用(使用 ?. 安全访问),useRef<number>(0) 用于可变值
  • 显式声明 useState<User | null>(null) 处理联合类型或空值
  • useReducer 动作使用区分联合类型:{ type: 'set'; payload: number } | { type: 'reset' }
  • useContext 空值保护:在自定义 useX() Hook 中,若上下文为 null 则抛出异常

Effects 决策树

Effects 是逃逸机制——大多数逻辑不应使用 Effects。

需求解决方案
从 props/state 推导出的值在渲染期间计算(如开销大则使用 useMemo)
prop 变化时重置状态通过组件的 key 属性实现
响应用户事件事件处理器
通知父组件状态变化在事件处理器中调用 onChange,或使用完全受控组件
状态更新链在一个事件处理器中一次性计算所有下一状态
与外部系统同步使用带清理函数的 Effect

Effect 规则:

  • 不要抑制 Linter 报告——应修复代码而非忽略
  • 使用更新函数(如 setItems(prev => [...prev, item]))以避免依赖项问题
  • 将对象/函数移入 Effects 内部,以稳定依赖项
  • 对非响应式值(如连接中的主题)使用 useEffectEvent
  • 总是返回清理函数,用于订阅、连接、监听器等场景
  • 数据获取取消策略(根据情况选择):AbortController 用于 fetch;ignore 标志用于不可取消的 Promise;React Query 自动处理两者

并发与竞态类别

共有五类竞态在类型检查和单元测试中存活——审查时需重点排查(清理/取消机制参考上述 Effect 规则):

类别生产环境信号修复方式
生命周期清理间隙“在已卸载组件上更新状态”警告,快速导航下的内存泄漏每个注册监听器/定时器/观察者的 Effect 必须返回清理函数
重新挂载时机错误异步回调在路由切换/卸载后修改状态/DOM(如 fetch().then(setData) 在导航后才 resolve)按取消层级进行取消操作
非二元 UI 的布尔状态矛盾组合(如 isLoading: true, error: Error使用状态常量(`'idle' \
过期的 Promise/定时器,无取消路径Promise 链或 setTimeout 在组件离开后仍持有 setState为每个异步操作绑定取消机制;测试清理路径
大列表中每项的事件处理器每行产生多个闭包/订阅,快速重渲染时出现过期闭包问题采用委托:仅父级设置一个处理器 + 使用 event.target.closest(...),当项目数超过 ~50 或更新频繁时适用

状态管理

本地 UI 状态       → useState, useReducer
共享客户端状态    → Zustand(简单) | Redux Toolkit(复杂)
原子化/细粒度状态   → Jotai
服务器/远程数据     → React Query(TanStack Query)
URL 状态            → nuqs, router search params
表单状态           → React Hook Form

关键模式:

  • Zustand:create<State>()(devtools(persist((set) => ({...})))) —— 使用 slices 分片管理规模;选择性订阅防止不必要的重渲染
  • React Query:查询键工厂(['users', 'detail', id] as const)、staleTime/gcTime、乐观更新配合 onMutate/onError 回滚
  • 永远不要在客户端存储(Zustand)中重复服务器数据(React Query)
  • 将状态与其使用位置就近关联

性能

关键 —— 消除瀑布流:

  • 对独立异步操作使用 Promise.all()
  • await 移至实际需要的位置
  • 使用 Suspense 边界实现慢内容流式加载

关键 —— 包体积优化:

  • 直接导入模块,避免 barrel 文件(如 index.ts 的重新导出)
  • 对重型组件使用 next/dynamicReact.lazy()
  • 延迟第三方脚本(分析、日志)直到 hydration 完成后
  • 在悬停/聚焦时预加载,提升感知速度
  • 长列表使用 content-visibility: auto + contain-intrinsic-size —— 跳过屏幕外布局/绘制

重渲染优化:

  • 在渲染期间推导状态,而非在 Effects 中
  • 订阅派生布尔值,而非原始对象(如 state.items.length > 0 而非 state.items
  • 使用函数式 setState 保持回调稳定性:setCount(c => c + 1)
  • 延迟初始化状态:useState(() => expensiveComputation())
  • 对非紧急更新使用 useTransition(如搜索过滤)
  • 对昂贵的派生 UI 使用 useDeferredValue
  • 不要在回调中仅读取 searchParams/state——按需读取
  • 条件渲染使用三元表达式(condition ? <A /> : <B />),而非 &&
  • React.memo 仅用于具有稳定 props 的昂贵子树
  • 将静态 JSX 提升到组件外部

React Compiler(React 19):自动记忆化——编写惯用 React 代码,移除手动 useMemo/useCallback/memo。通过 reactCompiler: truenext.config 中启用(非框架项目使用 babel-plugin-react-compiler)。保持组件纯净。

React 19

  • ref 作为普通属性 —— forwardRef 已弃用。直接接受 ref?: React.Ref<HTMLElement> 作为常规属性
  • useActionState —— 替代 useFormStateconst [state, formAction, isPending] = useActionState(action, initialState)
  • use() —— 在渲染期间解包 Promise 或 Context(不在回调或 Effects 中)。支持条件性上下文读取
  • useOptimistic —— const [optimistic, addOptimistic] = useOptimistic(state, mergeFn) 实现即时 UI 反馈
  • useFormStatus —— 在 <form action={...}> 的子组件中使用:const { pending } = useFormStatus()
  • 服务端组件 —— App Router 默认启用。支持异步操作,可直接访问数据库/密钥。不支持 Hooks 和事件处理器
  • 服务端动作 —— 使用 'use server' 指令。输入验证(如 Zod),revalidateTag/revalidatePath 在变更后触发。服务端动作是公开端点——必须在每个动作内部验证身份认证与授权,不能仅依赖中间件或布局守卫
  • **<Activity mode='visible'|'hidden'>** —— 保留切换组件的状态与 DOM(实验性)

Next.js App Router

文件约定:

page.tsx(路由界面)、layout.tsx(共享包装器)、template.tsx(导航时重新挂载,不同于 layout)、loading.tsx(Suspense)、error.tsx(错误边界)、not-found.tsx(404)、default.tsx(并行路由默认回退)、route.ts(API 端点)

渲染模式: 服务端组件(默认)|客户端('use client')|静态(构建时)|动态(请求时)|流式(渐进式)

决策原则: 除非需要 Hooks、事件处理器或浏览器 API,否则使用服务端组件。拆分:服务端父组件 + 客户端子组件。将交互式组件隔离为 'use client' 叶子组件——保持服务端组件静态,无全局状态或事件处理器。

服务端 → 客户端边界: 仅传递客户端组件实际使用的字段,而非整个 ORM 行或 fetch 对象。每个跨 'use client' 边界的 prop 都会被序列化为负载,因此即使只用一个字段,50 字段的 user 对象也会传输全部 50 个字段。

路由模式:

  • 路由组 (name) —— 组织结构但不影响 URL
  • 并行路由 @slot —— 同一布局中独立加载状态
  • 拦截路由 (.) —— 模态覆盖层,带完整页面回退

缓存策略:

  • fetch(url, { cache: 'force-cache' }) —— 静态缓存
  • fetch(url, { next: { revalidate: 60 } }) —— ISR(增量静态再生)
  • fetch(url, { cache: 'no-store' }) —— 动态请求
  • 标签缓存:fetch(url, { next: { tags: ['products'] } }) 后调用 revalidateTag('products')

数据获取:

  • 在使用数据的服务端组件中执行 fetch
  • 使用 Suspense 边界处理慢查询
  • 使用 React.cache() 实现每请求去重
  • 使用 generateStaticParams 进行静态生成
  • 使用 generateMetadata 实现动态 SEO
  • 静态元数据使用 title: { default: 'App', template: '%s | App' } 实现页面标题级联
  • 使用 after() 执行非阻塞副作用(如日志、分析)——在响应发送后运行
  • 将静态 I/O(字体、配置)提升至模块级别——仅执行一次,而非每次请求
  • 切勿在模块级可变状态中保存请求作用域或用户数据——服务端渲染在同一进程中并发执行,共享模块状态会导致请求间数据泄露(一个用户的数据出现在另一个用户的响应中)。仅提升不可变的静态 I/O;请求数据应保留在渲染树内(通过 props 传递)

测试(Vitest + React Testing Library)

  • 组件测试:Vitest + RTL,同名 *.test.tsx。React 组件的默认测试方式
  • Hook 测试:使用 renderHook + act,同名 *.test.ts
  • 单元测试:Vitest 用于纯函数、工具函数、服务层
  • 端到端测试:Playwright 用于用户流程和关键路径
  • 查询优先级getByRole > getByLabelText > getByPlaceholderText > getByText > getByTestId
  • 模拟 API 服务与外部提供者;真实渲染子组件以保证集成信心
  • 每个测试只验证一种行为,遵循 AAA 结构(Arrange, Act, Assert)。命名格式:should <行为> when <条件>
  • 使用 userEvent 而非 fireEvent,以模拟真实交互
  • 对异步元素使用 findBy*,对状态触发操作后使用 waitFor
  • beforeEach 中使用 vi.clearAllMocks(),每次测试重建状态
  • 通用测试规范(反模式、理性抵抗)参见 [ia-writing-tests](../ia-writing-tests/SKILL.md) 技能。
  • 参考 [testing.md](./references/testing.md) 获取组件、Hook 及模拟示例。
  • 参考 [e2e-testing.md](./references/e2e-testing.md) 获取 Playwright 测试模式。

Tailwind 集成

关于 Tailwind v4 配置、实用类模式、暗色模式及组件变体,请参考 [ia-tailwind-css](../ia-tailwind-css/SKILL.md) 技能。

JSX 中的类名排序:保持 Tailwind 类名按标准顺序排列(可通过 eslint-plugin-better-tailwindcss 强制执行)。

规范纪律

  • 优先简洁——每次变更尽可能简单,影响最小范围的代码
  • 仅修改必要部分——避免引入无关更改
  • 禁止投机取巧的绕过方案——若修复感觉不对,应退回并实现清晰解决方案
  • 在引入新抽象前,确认其已在 3 个以上位置出现

参考资料

  • [testing.md](./references/testing.md) —— 组件、Hook 及模拟测试示例
  • [e2e-testing.md](./references/e2e-testing.md) —— Playwright 端到端测试模式

验证清单

  • TypeScript 编译无任何错误
  • 新代码中无被抑制的 lint 规则(如 eslint-disable@ts-ignore
  • useEffect 依赖数组未被手动覆盖
  • React 19+ 项目中无 forwardRef 使用(应直接使用 ref 属性)
I
@iliaal

已收录 3 个 Skill

相关推荐