mcpField
R
react-best-practices

react-best-practices

github.com/pixel-process-ug开发379 次安装34 周前更新
代码仓库

关于 react-best-practices

react-best-practices 是面向 React 开发的技能,帮助构建易维护、可测试的应用。内容涵盖 React 18/19 模式、组件设计、测试与基于测量的性能优化。

主要功能

  1. 01选择服务端或客户端组件
  2. 02Hooks 与组件组合模式
  3. 03错误边界与 Suspense
  4. 04Context 与渲染优化
  5. 05面向行为的无障碍测试

适用场景

  1. 01划分组件的数据与状态职责
  2. 02设计可复用的 React 组件
  3. 03测试加载、错误与交互状态

安装

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

$npx skills add https://github.com/pixel-process-ug/superkit-agents --skill react-best-practices

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

详细介绍

React 最佳实践

概述

应用现代 React 模式,构建易于维护、性能出色且可测试的应用程序。本技能涵盖 React 18/19 的功能,包括服务端组件、Hooks 最佳实践、组件组合、错误边界、Suspense、Context 优化和渲染性能。本技能补充 senior-frontend 技能,深入介绍 React 专属内容。

开始时请说明:「我将使用 react-best-practices 技能来介绍 React 专属模式。」


阶段 1:分析组件需求

目标: 编码前了解组件的职责和数据需求。

操作
  1. 确定组件的单一职责
  2. 确定数据需求(服务端数据还是客户端数据)
  3. 选择服务端组件(默认)或客户端组件
  4. 确定状态管理需求
  5. 规划错误和加载状态
服务端与客户端决策表
需求组件类型原因
直接获取数据(DB、API)服务端(默认)无需客户端 JS,速度更快
事件处理器(onClick、onChange)客户端('use client')需要浏览器交互能力
useState / useReducer客户端状态需要客户端运行时
useEffect / useLayoutEffect客户端副作用需要客户端
浏览器 API(window、localStorage)客户端服务端没有浏览器
使用客户端功能的第三方库客户端该库需要客户端
无需交互能力服务端(默认)包体积更小,速度更快
停止 — 完成以下事项前不要进入阶段 2:
  • 已定义组件职责(单一用途)
  • 已做出服务端与客户端的选择,并说明理由
  • 已梳理数据需求

阶段 2:使用恰当的模式实现

目标: 根据组件需求应用正确的 React 模式。

操作
  1. 应用恰当的组合模式
  2. 正确实现 Hooks
  3. 添加错误边界和 Suspense
  4. 根据性能分析结果优化渲染
  5. 编写验证行为的测试
停止 — 完成以下事项前不要进入阶段 3:
  • 模式符合组件的实际需求
  • 没有不必要的复杂性(不进行过早优化)
  • 测试覆盖用户可见的行为

阶段 3:测试与验证

目标: 通过测试验证组件行为。

操作
  1. 使用可访问性查询编写测试
  2. 测试用户交互及其结果
  3. 测试错误和加载状态
  4. 验证可访问性
查询优先级(React Testing Library)
优先级查询用途
第 1 优先级getByRole任何带 ARIA 角色的元素
第 2 优先级getByLabelText表单字段
第 3 优先级getByPlaceholderText没有标签的字段
第 4 优先级getByText非交互元素
最后getByTestId其他方法均不适用时
停止 — 满足以下条件即表示测试完成:
  • 用户交互产生预期结果
  • 已测试错误状态
  • 可访问性检查通过

Hooks 最佳实践

useState
// Functional updates for state based on previous state
setCount(prev => prev + 1);

// Lazy initialization for expensive initial values
const [data, setData] = useState(() => computeExpensiveInitialValue());

// Group related state
const [form, setForm] = useState({ name: '', email: '', role: 'user' });
useEffect
依赖数组规则
  • 包含组件作用域内所有会随时间变化的值
  • 在 effect 内部定义函数,或使用 useCallback 包装函数
  • 绝不要谎报依赖项(ESLint:react-hooks/exhaustive-deps)
清理模式
useEffect(() => {
  const controller = new AbortController();
  async function fetchData() {
    try {
      const res = await fetch(url, { signal: controller.signal });
      const data = await res.json();
      setData(data);
    } catch (e) {
      if (e.name !== 'AbortError') setError(e);
    }
  }
  fetchData();
  return () => controller.abort();
}, [url]);
何时不应使用 useEffect
不使用 useEffect 的场景……改用
获取数据React Query、SWR 或服务端组件
转换数据在渲染期间计算
用户事件事件处理器
同步外部存储useSyncExternalStore
自定义 Hooks 规则
  • 名称以 use 开头
  • 封装可复用的有状态逻辑
  • 每个关注点使用一个 Hook
  • 超过 2 个值时返回对象(而非数组)
function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);
  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);
  return debouncedValue;
}

组件组合模式

复合组件
function Tabs({ children, defaultValue }: TabsProps) {
  const [activeTab, setActiveTab] = useState(defaultValue);
  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      <div role="tablist">{children}</div>
    </TabsContext.Provider>
  );
}

Tabs.Tab = function Tab({ value, children }: TabProps) {
  const { activeTab, setActiveTab } = useTabsContext();
  return (
    <button role="tab" aria-selected={activeTab === value} onClick={() => setActiveTab(value)}>
      {children}
    </button>
  );
};

Tabs.Panel = function Panel({ value, children }: PanelProps) {
  const { activeTab } = useTabsContext();
  if (activeTab !== value) return null;
  return <div role="tabpanel">{children}</div>;
};
组合方式决策表
模式适用场景示例
复合组件相关组件共享隐式状态标签页、手风琴、菜单
插槽(子组件)复杂内容布局包含页眉/主体/页脚的卡片
Render Props子组件需要父组件数据以灵活渲染使用自定义渲染的 DataFetcher
高阶组件横切关注点(旧版)withAuth、withTheme
自定义 Hook无需 UI 的可复用有状态逻辑useDebounce、useLocalStorage
插槽模式
// Prefer composition over props for complex content
// Bad
<Card title="Hello" subtitle="World" icon={<Star />} actions={<Button>Edit</Button>} />

// Good
<Card>
  <Card.Header>
    <Card.Icon><Star /></Card.Icon>
    <Card.Title>Hello</Card.Title>
  </Card.Header>
  <Card.Actions>
    <Button>Edit</Button>
  </Card.Actions>
</Card>

错误边界

放置策略决策表
级别用途示例
路由级别捕获页面级崩溃Next.js 中的 error.tsx
功能级别隔离功能故障为每个主要部分添加包装
数据级别包装异步数据组件放在 Suspense 边界周围
不要放在叶子级别粒度过细,会增加冗余不要为单个按钮添加包装

Suspense

// Nested Suspense for granular loading
<Suspense fallback={<PageSkeleton />}>
  <Header />
  <Suspense fallback={<SidebarSkeleton />}>
    <Sidebar />
  </Suspense>
  <Suspense fallback={<ContentSkeleton />}>
    <MainContent />
  </Suspense>
</Suspense>

Context 优化

问题:Context 会导致不必要的重新渲染
解决方案决策表
技术适用场景示例
按更新频率拆分 Context部分值更新频繁,部分值很少更新ThemeContext(较少更新)与 UIStateContext(频繁更新)
对 Context 值进行记忆化Provider 使用相同数据重新渲染useMemo(() => ({ state, dispatch }), [state])
使用选择器(Zustand/Jotai)需要细粒度订阅useStore(state => state.user.name)
向上提升状态只有父组件需要重新渲染将数据作为 props 传给记忆化子组件

渲染优化

记忆化决策表
技术适用场景不适用场景
React.memo使用相同 props 频繁渲染,且重新渲染开销大每次渲染时 props 都会变化
useMemo开销大的计算,或依赖项需要引用相等性简单计算
useCallback为记忆化子组件提供稳定的函数引用函数不会作为 prop 传递
无(默认)始终从这里开始过早优化

规则: 记忆化之前先进行性能分析。过早记忆化是最常见的 React 反模式。

虚拟化

对于超过 100 项的列表:

import { useVirtualizer } from '@tanstack/react-virtual';

Server Component 规则

  • 不能使用 hooks
  • 不能使用浏览器 API
  • 不能将函数作为 props 传给 Client Components
  • 可以导入并渲染 Client Components
  • 可以向 Client Components 传递可序列化数据
// Server Component — fetches data directly
async function UserProfile({ userId }: { userId: string }) {
  const user = await db.user.findUnique({ where: { id: userId } });
  return (
    <div>
      <h1>{user.name}</h1>
      <UserActions userId={userId} /> {/* Client Component child */}
    </div>
  );
}

// Client Component — handles interactivity
'use client';
function UserActions({ userId }: { userId: string }) {
  const [isFollowing, setIsFollowing] = useState(false);
  return <Button onClick={() => toggleFollow(userId)}>Follow</Button>;
}

反模式/常见错误

反模式错误原因正确做法
使用 useEffect 获取数据竞态条件、没有缓存、无法去重使用 React Query 或 Server Components
props 逐层传递超过 2 层耦合紧密、维护困难使用组合、Context 或 Zustand
存储派生状态可以计算得出的状态是不必要的状态在渲染期间计算
使用 useEffect 从 props 同步状态不必要的 effect、闭包过期在渲染期间派生状态,或使用 key prop
单体组件(超过 200 行)难以阅读、测试和维护提取子组件
动态列表使用索引作为 key协调错误、状态过期使用稳定的唯一 ID
直接操作 DOM绕过 React 协调机制谨慎使用 refs,优先使用状态
直接测试状态值属于实现细节,重构时容易失效测试用户可见的结果
对所有内容进行记忆化增加复杂性,且通常更慢先分析性能,再进行优化

文档查询(Context7)

使用 mcp__context7__resolve-library-id,然后使用 mcp__context7__query-docs 获取最新文档。返回的文档优先于记忆中的知识。

  • react — 用于 hooks、context、suspense、server components 或 React 19+ 的变更
  • next.js — 用于 App Router 模式、数据获取或 server actions

集成点

技能关系
senior-frontend前端技能使用本技能中的 React 模式
testing-strategyReact 测试遵循策略金字塔
clean-code组件代码遵循整洁代码原则
performance-optimizationReact 渲染优化遵循测量方法
webapp-testing端到端测试验证 React 组件行为
code-review代码审查检查 React 反模式
acceptance-testingUI 验收标准驱动组件测试

技能类型

灵活 — 根据具体的 React 版本、项目结构和团队惯例应用这些模式。原则是一致的,但实现细节可能有所不同。优化前务必先进行性能分析。

评论

支持 Markdown