mcpField
T
tech-react

tech-react

github.com/ravnhq开发1658 次安装21 周前更新
代码仓库

关于 tech-react

为 React 开发者提供组件、Hooks 和 Server/Client 边界方面的实践指南,涉及 React 19 特性。内容强调 Suspense、稳定列表键以及先测量再优化。

主要功能

  1. 01Hooks 调用规则
  2. 02Server 与 Client 组件划分
  3. 03Suspense 异步处理
  4. 04稳定的列表键
  5. 05性能测量与优化

适用场景

  1. 01重构 Hooks 使用方式
  2. 02将客户端数据获取迁移到 Server Components
  3. 03排查列表重排后的状态问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ravnhq/ai-toolkit --skill tech-react

安装后在 Claude Code 中输入/tech-react确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

rules/
_sections.md
component-no-mutation-callbacks.md
component-semantic-elements.md
component-use-key-for-reset.md
effect-cleanup.md
effect-when-not-needed.md
hooks-rules.md
hooks-use-for-promises.md
perf-no-premature-memo.md
perf-stable-keys.md
server-client-boundaries.md

详细介绍

核心挑战

React 的模式会随着每个主要版本不断演进。React 19 引入了用于处理 Promise 的 use() Hook,并通过更明确的客户端/服务端语义,正式确立了 Server Component 的边界。常见问题包括:

  • Hook 调用顺序违规:有条件地调用 Hook 会破坏 React 的追踪机制
  • 不必要的记忆化:useMemo/useCallback/React.memo 未经测量就会带来额外开销
  • 组件过于庞大:混合 Server 和 Client 逻辑会妨碍流式优化
  • 缺少 Suspense 边界:没有 Suspense 的异步数据会阻塞整个渲染过程
  • 闭包过期:Effect 的依赖数组不正确,或缺少清理逻辑
  • 错误使用 key:使用索引作为 key 时,列表重排后状态会关联到错误的列表项

React 19 改进了这些模式:使用 use() Hook 消费 Promise,使用 Server Components 获取数据而无需额外请求,并使用 useTransition() 执行非阻塞更新。

工作流

  1. 明确组件职责:判断组件应为 Server(数据密集型)还是 Client(交互型)
  2. 在顶层定义 Hook:所有 Hook 都必须无条件执行,并且位于任何 return 语句之前
  3. 使用 Suspense 处理异步操作:用 Suspense 边界包裹 use() Hook 调用,并提供加载回退内容
  4. 使用 key 进行组合:为列表项使用稳定且唯一的 key;使用 key 属性重置组件状态
  5. 先测量,再优化:在添加记忆化之前,先使用 React DevTools 进行性能分析
  6. 清理订阅:订阅系统的 Effect 必须始终返回清理函数
  7. 保持组件精简:提取 Client Components 处理交互,让 Server Components 处理数据

规则

详细模式请参见 规则索引。

示例

正向触发

用户:“重构这个 React 组件,减少重新渲染并让 Hook 的使用更清晰。”

预期行为:使用 tech-react 指南,遵循其工作流,并提供可执行的结果。

正向触发:Server Component 边界

用户:“我正在客户端使用 useEffect 获取数据。该如何使用 Server Components 重构?”

预期行为:将数据获取移至 Server Component,通过 use() Hook 将 Promise 传递给 Client Component,并使用 Suspense 边界包裹。

非触发

用户:“编写一个 Bash 脚本来打包发布构件。”

预期行为:不要优先选择 tech-react;改选更相关的技能,或在不使用该技能的情况下继续处理。

故障排除

Hook 调用违规(“Rendered more hooks than during the previous render”)
  • 错误:React 抛出“Rendered fewer/more hooks than during the previous render”
  • 原因:在条件、循环或提前 return 语句中调用 Hook
  • 解决方案:将 Hook 调用移到任何条件逻辑之前;使用数据 Hook 的 enabled 选项跳过执行
Suspense 回退内容始终不显示
  • 错误:use() 触发挂起时,加载回退内容没有出现
  • 原因:Suspense 边界没有包裹调用 use() 的组件
  • 解决方案:确保 <Suspense> 是该组件的父级,而非兄弟组件
Server Component 无法使用状态或事件
  • 错误:useState、onClick 处理函数在 Server Components 中不起作用
  • 原因:Server Components 只在服务端渲染一次;它们无法响应客户端交互
  • 解决方案:使用 "use client" 指令,将交互部分提取到 Client Components 中;由 Server Component 处理数据获取,并将数据传递给 Client Component
useEffect 运行两次或造成内存泄漏
  • 错误:Effect 副作用运行多次;清理函数未执行
  • 原因:缺少依赖数组,或缺少返回的清理函数
  • 解决方案:添加依赖数组;为订阅/定时器/监听器返回清理函数
列表项失去焦点或顺序错乱
  • 错误:输入框焦点在行之间跳转;排序后列表项顺序错乱
  • 原因:使用数组索引作为 key,而非稳定且唯一的标识符
  • 解决方案:将 key={index} 改为 key={item.id},使用数据中的唯一属性

Examples: Error Patterns

Error 1: Conditional Hook

Incorrect:

function UserProfile({ userId }: { userId: string | null }) {
  if (!userId) {
    return <div>Select a user</div>;
  }

  // Hook called conditionally - React can't track it!
  const [profile, setProfile] = useState(null);
  return <div>{profile?.name}</div>;
}

Correct:

function UserProfile({ userId }: { userId: string | null }) {
  const [profile, setProfile] = useState(null);

  // Early return AFTER hooks
  if (!userId) {
    return <div>Select a user</div>;
  }

  return <div>{profile?.name}</div>;
}
Error 2: Suspense Without use() Hook

Incorrect:

function Comments({ id }: { id: string }) {
  const [comments, setComments] = useState([]);

  useEffect(() => {
    fetchComments(id).then(setComments);
  }, [id]);

  return <ul>{comments.map(c => <li key={c.id}>{c.text}</li>)}</ul>;
}

Correct:

function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
  const comments = use(commentsPromise);
  return <ul>{comments.map(c => <li key={c.id}>{c.text}</li>)}</ul>;
}

function CommentsSection({ id }: { id: string }) {
  return (
    <Suspense fallback={<div>Loading comments...</div>}>
      <Comments commentsPromise={fetchComments(id)} />
    </Suspense>
  );
}
Error 3: Server/Client Boundary Confusion

Incorrect:

// BAD: Server Component with useState
async function NotesPage() {
  const [selectedNote, setSelectedNote] = useState(null); // ERROR: Can't use state
  const notes = await db.notes.getAll();

  return (
    <div>
      {notes.map(note => (
        <button onClick={() => setSelectedNote(note)}>
          {note.title}
        </button>
      ))}
    </div>
  );
}

Correct:

// Server Component - fetch data
async function NotesPage() {
  const notes = await db.notes.getAll();

  return (
    <div>
      <NotesList notes={notes} />
    </div>
  );
}

// Client Component - handle interaction
"use client";

function NotesList({ notes }: { notes: Note[] }) {
  const [selectedNote, setSelectedNote] = useState<Note | null>(null);

  return (
    <div>
      {notes.map(note => (
        <button
          key={note.id}
          onClick={() => setSelectedNote(note)}
        >
          {note.title}
        </button>
      ))}
    </div>
  );
}

评论

支持 Markdown