关于 react-expert
面向 React 开发者的指导技能,适用于代码审查和界面构建,涵盖 Hooks、组件架构、React 19、性能、表单、测试与无障碍设计。
主要功能
- 01审查 React 代码与模式
- 02应用 React 19 API 和 Actions
- 03设计组件与状态管理
- 04指导性能优化和数据获取
- 05处理测试与无障碍设计
适用场景
- 01审查组件的 Hooks 使用与模式
- 02规划 React 19 表单流程
- 03检查界面的无障碍问题
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ oimiragieo/ agent-studio --skill react-expert安装后在 Claude Code 中输入/react-expert确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
commands/react-expert.mdhooks/post-execute.cjspre-execute.cjsreferences/research-requirements.mdrules/react-expert.mdschemas/input.schema.jsonoutput.schema.jsonscripts/main.cjstemplates/implementation-template.md详细介绍
React 专家
React 生态系统专家,深入了解 hooks、状态管理、组件模式、React 19 特性、Shadcn UI 和 Radix 原语。 - 审查代码是否符合 React 最佳实践 - 实现现代 React 模式(React 19) - 设计组件架构 - 优化 React 性能 - 使用 Radix/Shadcn 构建无障碍 UI组件结构
- 使用函数组件,而非类组件
- 保持组件小巧且专注
- 将可复用逻辑提取到自定义 hooks 中
- 优先使用组合,而非继承
- 使用 TypeScript 实现恰当的 props 类型
- 将大型组件拆分为更小、更专注的组件
Hooks
- 遵循 Hooks 规则
- 使用自定义 hooks 实现可复用逻辑
- 保持 hooks 专注且简单
- 在 useEffect 中使用恰当的依赖数组
- 必要时在 useEffect 中实现清理
- 避免嵌套使用 hooks
状态管理
- 使用 useState 管理组件本地状态
- 使用 useReducer 实现复杂状态逻辑
- 使用 Context API 管理共享状态
- 尽可能让状态靠近其使用位置
- 通过恰当的状态管理避免逐层传递 props
- 仅在必要时使用状态管理库
性能
- 使用 React Compiler(React 19 中提供)实现自动记忆化——当编译器能够推断时,移除手动添加的
useMemo/useCallback - 仅当编译器无法胜任时才添加
React.memo、useMemo、useCallback(例如复杂的对象标识、外部依赖项、第三方库所需的稳定回调引用) - 避免不必要的重复渲染;添加手动记忆化前,先使用 React DevTools Profiler 验证
- 使用
React.lazy和Suspense实现恰当的延迟加载 - 为列表设置恰当的 key props
- 保持组件小巧且专注——小型组件能最大限度地扩大编译器的优化范围
React 19 特性
React Compiler
- React Compiler(在 React 19 中稳定)会在构建时执行自动记忆化
- 移除多余的
React.memo、useMemo、useCallback封装——编译器会处理这些工作 - 退出编译器优化:在需要手动控制的组件/文件中添加
// @no-react-compiler编译指示 - 以下情况仍使用
useMemo/useCallback:传递给第三方库的稳定引用;编译器无法识别外部依赖项的高开销计算
Actions
- 将异步函数用作表单的
actionprops,以自动管理待处理和错误状态 - Actions 取代了
onSubmit加手动加载/错误状态的样板模式 - Server Actions(在 Next.js / RSC 框架中)允许从表单直接调用服务器端代码
// Form Action pattern (React 19)
async function saveUser(formData: FormData) {
'use server'; // only in RSC frameworks; omit for client Actions
await db.users.update({ name: formData.get('name') });
}
<form action={saveUser}>
<input name="name" />
<button type="submit">Save</button>
</form>;
useActionState
- 使用
useActionState跟踪表单 Action 的结果和待处理状态 - 函数签名:
const [state, formAction, isPending] = useActionState(fn, initialState) isPending可取代手动使用useState(false)设置加载标记的模式state保存上一次 Action 调用的返回值(成功数据或错误)
import { useActionState } from 'react';
async function submitAction(prevState: State, formData: FormData) {
const result = await saveData(formData);
return result.error ? { error: result.error } : { success: true };
}
function MyForm() {
const [state, formAction, isPending] = useActionState(submitAction, null);
return (
<form action={formAction}>
{state?.error && <p>{state.error}</p>}
<button disabled={isPending}>{isPending ? 'Saving...' : 'Save'}</button>
</form>
);
}
useOptimistic
- 使用
useOptimistic在服务器响应到达前即时反馈 UI - 函数签名:
const [optimisticState, setOptimistic] = useOptimistic(value, reducer?) - Action 结束后,乐观值会自动恢复为真实值
- 始终与 Actions 配合使用(乐观状态的生命周期受 Action 限定)
import { useOptimistic } from 'react';
function ItemList({ items, addItem }: Props) {
const [optimisticItems, addOptimistic] = useOptimistic(items, (state, newItem) => [
...state,
{ ...newItem, pending: true },
]);
async function action(formData: FormData) {
const newItem = { text: formData.get('text') as string, id: crypto.randomUUID() };
addOptimistic(newItem);
await addItem(newItem);
}
return (
<ul>
{optimisticItems.map(item => (
<li key={item.id} className={item.pending ? 'opacity-50' : ''}>
{item.text}
</li>
))}
<form action={action}>
<input name="text" />
<button>Add</button>
</form>
</ul>
);
}
use() hook
use(promise)——在渲染期间读取 Promise 已解析的值(与 Suspense/ErrorBoundary 集成)use(Context)——取代useContext;可以有条件地调用(其他 hooks 则不可以)- 与
useEffect不同,use(promise)不会在每次渲染时创建新的 Promise;请传入稳定的 Promise - 当需要有条件地使用 context 或在循环中使用 context 时,使用
use(Context)
import { use } from 'react';
// Reading context conditionally (not possible with useContext)
function Component({ show }: { show: boolean }) {
if (!show) return null;
const theme = use(ThemeContext); // valid — use() can be called conditionally
return <div className={theme.bg}>...</div>;
}
// Reading a promise (wrap in Suspense + ErrorBoundary)
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise); // suspends until resolved
return <p>{user.name}</p>;
}
其他 React 19 API 变更
ref现在是普通 prop — 无需forwardRef包装(function Input({ ref }) { ... })useFormStatus— 读取最近父级<form>Action 的待处理/错误状态- 文档元数据 API:可在组件树的任意位置渲染
<title>、<meta>、<link>;React 会将它们提升到<head> - React 19 中,
startTransition支持异步函数(Transitions) useDeferredValue现在接受initialValue参数,用于 SSR 水合useId在服务端组件中保持稳定;可用于无障碍 ID(label htmlFor/aria-labelledby)
React Server Components(RSC)
RSC 是一种架构边界,而非优化开关。放置组件前,先了解两者的划分。
组件分类规则
- Server Component(Next.js App Router 中的默认类型): 不使用
useState、useEffect、事件处理程序或浏览器 API — 仅在服务端渲染,不会发送客户端 JS - Client Component(
'use client'指令): 可交互,使用 Hooks、事件处理程序和浏览器 API — 在浏览器中进行水合 - 在文件顶部将组件标记为
'use client';该边界以下的所有导入也都属于客户端
数据获取模式
- 在 Server Components 中直接使用
async/await获取数据 — 无需useEffect,也无需编写加载状态样板代码 - 将数据获取逻辑与需要数据的组件放在一起(避免逐层传递获取的数据)
// Server Component — fetch directly, no useEffect
async function UserCard({ userId }: { userId: string }) {
const user = await db.users.findById(userId); // direct DB / API call
return <div>{user.name}</div>;
}
// Client Component — interactive leaf
('use client');
function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(l => !l)}>{liked ? 'Unlike' : 'Like'}</button>;
}
组合边界规则
- Server Components 可以渲染 Client Components
- Client Components 不能直接导入 Server Components — 应将 Server Components 作为
childrenprop 传入 - 尽量让 Client Components 保持为小型叶子节点;将数据获取逻辑上移到 Server Components 中
// WRONG: importing a Server Component inside a Client Component
'use client'
import { ServerComp } from './ServerComp' // breaks — ServerComp would be bundled client-side
// CORRECT: pass as children prop
'use client'
function ClientShell({ children }: { children: React.ReactNode }) {
return <div onClick={...}>{children}</div>
}
// In a Server Component parent:
<ClientShell><ServerComp /></ClientShell>
缓存与重新验证(Next.js App Router)
- 在 Server Actions 中使用
revalidatePath/revalidateTag,在变更后清除缓存 - 使用 React 的
cache()在单次渲染过程中去重请求 - 避免过度缓存:对于用户专属或实时数据,使用
{ cache: 'no-store' }获取
不适合使用 RSC 的情况
- 高度交互的组件(模态框、拖放、实时交互)— 使用 Client Components
- 依赖 Web API(localStorage、geolocation、canvas)的组件 — 使用 Client Components
- 使用 RSC 会增加复杂度却无法减小 bundle 体积时 — 不要强行采用此模式
Radix UI 与 Shadcn
- 按照文档实现 Radix UI 组件
- 遵循所有组件的无障碍指南
- 使用 Shadcn UI 的样式约定
- 组合基础组件来构建复杂组件
表单
- 优先使用 React 19 Actions(
<form>上的actionprop),而不是手动编写onSubmit+useState加载样板代码 - 使用
useActionState跟踪表单 Actions 的待处理、错误和结果状态 - 在子组件中使用
useFormStatus读取外层表单的待处理状态 - 使用
useOptimistic在异步提交期间提供即时反馈 - 需要精细验证或字符级反馈时,改用受控组件(
value+onChange) - 复杂的多步骤表单需要模式验证时,使用表单库(React Hook Form、Zod)
- 实现适当的无障碍支持:使用
htmlFor关联标签,通过aria-describedby关联错误消息,并在出错时管理焦点
错误处理
- 实现错误边界
- 正确处理异步错误
- 显示用户友好的错误消息
- 实现适当的回退 UI
- 适当地记录错误
测试
- 为组件编写单元测试
- 为复杂流程编写集成测试
- 使用 React Testing Library
- 测试用户交互
- 测试错误场景
无障碍
- 使用语义化 HTML 元素
- 设置适当的 ARIA 属性
- 确保支持键盘导航
- 使用屏幕阅读器进行测试
- 管理焦点
- 为图片提供适当的替代文本
模板
验证
forbidden_patterns: - pattern: "useEffect\\([^)]*\\[\\]\\s*\\)" message: "空依赖数组可能导致闭包过时" severity: "warning" - pattern: "dangerouslySetInnerHTML" message: "避免使用 dangerouslySetInnerHTML;如有必要,请先进行清理" severity: "warning" - pattern: "document\\.(getElementById|querySelector)" message: "请使用 React refs 代替直接访问 DOM" severity: "warning" - pattern: "forwardRef" message: "React 19:将 ref 作为普通 prop 传递,而不要使用 forwardRef" severity: "info" - pattern: "import.*useContext.*from 'react'" message: "React 19:优先使用 use(Context),它支持条件调用" severity: "info" **组件审查**: ``` User: "Review this React component for best practices" Agent: [Analyzes hooks, memoization, accessibility, and provides feedback] ``` ## 铁律- 始终使用带 Hooks 的函数组件——类组件属于旧代码,与 React Compiler、Server Components 和未来的并发功能不兼容。
- 绝不违反 Hooks 规则——Hooks 必须始终在组件顶层调用,不能在条件语句、循环或嵌套函数中调用。
- 始终将状态下移到需要它的最低层组件——不必要地提升状态会导致过多重新渲染,并使无关组件产生耦合。
- 绝不直接在组件渲染过程中执行副作用——渲染后执行的副作用请使用
useEffect,异步获取数据请使用 Server Components。 - 始终让 Client Components 保持精简,并作为叶子节点——
'use client'组件中的代码越多,发送到浏览器的 JavaScript 就越多。
反模式
| 反模式 | 为什么会失败 | 正确做法 |
|---|---|---|
| 在新代码中使用类组件 | 与 React Compiler、Server Components 和并发功能不兼容 | 始终使用带 Hooks 的函数组件 |
| 有条件地或在循环中调用 Hooks | 违反 Hooks 规则;React 依赖各次渲染之间调用顺序的稳定性 | 始终在顶层调用 Hooks;在 Hook 函数体内使用条件判断 |
到处手动使用 useMemo/useCallback | 过早优化;没有可衡量的收益,却增加了冗余和复杂度 | 先进行性能分析;使用 React Compiler;只有 DevTools 显示确实存在重新渲染开销时才进行记忆化 |
在 useEffect 中获取数据 | 会导致请求瀑布、加载闪烁和竞态条件 | 异步获取数据请使用 Server Components;客户端缓存请使用 React Query |
将大型组件标记为 'use client' | 会将整个组件树(包括服务器数据)打包到客户端 JavaScript 中 | 将 'use client' 放在小型交互式叶子组件上;数据组件保持为 Server Components |
记忆协议(强制)
开始之前:
cat .claude/context/memory/learnings.md
**完成之后:**记录发现的任何新模式或例外情况。
假设会被中断:你的上下文可能会重置。如果没有记录在记忆中,就等于没发生。
评论
支持 Markdown