react-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact Best Practices
React最佳实践
Overview
概述
Apply modern React patterns to build maintainable, performant, and testable applications. This skill covers React 18/19 features including Server Components, hooks best practices, component composition, error boundaries, Suspense, context optimization, and rendering performance. It complements the senior-frontend skill with React-specific depth.
Announce at start: "I'm using the react-best-practices skill for React-specific patterns."
应用现代React模式来构建可维护、高性能、可测试的应用。本技能覆盖React 18/19特性,包括Server Components、hooks最佳实践、组件组合、错误边界、Suspense、context优化和渲染性能。它为高级前端技能提供React专属的深度内容补充。
开场提示: "我正在使用react-best-practices技能提供React专属模式相关的支持。"
Phase 1: Analyze Component Requirements
阶段1:分析组件需求
Goal: Understand the component's responsibility and data requirements before coding.
目标: 编码前明确组件的职责和数据需求。
Actions
操作步骤
- Identify the component's single responsibility
- Determine data requirements (server vs client data)
- Choose Server Component (default) or Client Component
- Identify state management needs
- Plan error and loading states
- 明确组件的单一职责
- 确定数据需求(服务端数据/客户端数据)
- 选择Server Component(默认)或Client Component
- 识别状态管理需求
- 规划错误和加载状态
Server vs Client Decision Table
服务端vs客户端组件选型表
| Need | Component Type | Reason |
|---|---|---|
| Direct data fetching (DB, API) | Server (default) | No client JS, faster |
| Event handlers (onClick, onChange) | Client ( | Needs browser interactivity |
| useState / useReducer | Client | State requires client runtime |
| useEffect / useLayoutEffect | Client | Side effects require client |
| Browser APIs (window, localStorage) | Client | Server has no browser |
| Third-party libs using client features | Client | Library requires client |
| No interactivity needed | Server (default) | Smaller bundle, faster |
| 需求 | 组件类型 | 原因 |
|---|---|---|
| 直接获取数据(DB、API) | Server(默认) | 无客户端JS,速度更快 |
| 事件处理(onClick、onChange) | Client( | 需要浏览器交互能力 |
| useState / useReducer | Client | 状态需要客户端运行时支持 |
| useEffect / useLayoutEffect | Client | 副作用需要客户端环境 |
| 浏览器API(window、localStorage) | Client | 服务端无浏览器环境 |
| 使用客户端特性的第三方库 | Client | 库依赖客户端环境 |
| 无需交互能力 | Server(默认) | 打包体积更小,速度更快 |
STOP — Do NOT proceed to Phase 2 until:
停止 —— 满足以下条件前不要进入阶段2:
- Component responsibility is defined (single purpose)
- Server vs Client decision is made with rationale
- Data requirements are mapped
- 组件职责已明确(单一用途)
- 已完成服务端/客户端组件选型并说明理由
- 已梳理清楚数据需求
Phase 2: Implement with Appropriate Patterns
阶段2:使用合适的模式实现
Goal: Apply the correct React patterns for the component's needs.
目标: 针对组件需求应用正确的React模式。
Actions
操作步骤
- Apply appropriate composition pattern
- Implement hooks correctly
- Add error boundaries and Suspense
- Optimize rendering where profiling shows need
- Write tests that verify behavior
- 应用合适的组合模式
- 正确实现hooks
- 添加错误边界和Suspense
- 在 Profiler 显示有需要的地方优化渲染
- 编写验证组件行为的测试用例
STOP — Do NOT proceed to Phase 3 until:
停止 —— 满足以下条件前不要进入阶段3:
- Patterns match the component's actual needs
- No unnecessary complexity (no premature optimization)
- Tests cover user-visible behavior
- 所用模式匹配组件实际需求
- 无不必要的复杂度(无过早优化)
- 测试覆盖用户可见的行为
Phase 3: Test and Verify
阶段3:测试与验证
Goal: Verify component behavior through tests.
目标: 通过测试验证组件行为。
Actions
操作步骤
- Write tests using accessible queries
- Test user interactions and outcomes
- Test error and loading states
- Verify accessibility
- 使用可访问查询编写测试
- 测试用户交互和输出结果
- 测试错误和加载状态
- 验证可访问性
Query Priority (React Testing Library)
查询优先级(React Testing Library)
| Priority | Query | Use For |
|---|---|---|
| 1st | | Any element with ARIA role |
| 2nd | | Form fields |
| 3rd | | Fields without labels |
| 4th | | Non-interactive elements |
| Last | | When nothing else works |
| 优先级 | 查询方法 | 适用场景 |
|---|---|---|
| 1st | | 所有带ARIA角色的元素 |
| 2nd | | 表单字段 |
| 3rd | | 无标签的输入字段 |
| 4th | | 非交互元素 |
| 最后 | | 其他查询都不适用的场景 |
STOP — Testing complete when:
停止 —— 满足以下条件时测试完成:
- User interactions produce expected outcomes
- Error states are tested
- Accessibility checks pass
- 用户交互产生预期结果
- 错误状态已测试
- 可访问性检查通过
Hooks Best Practices
Hooks最佳实践
useState
useState
typescript
// 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' });typescript
// 基于前序状态更新state时使用函数式更新
setCount(prev => prev + 1);
// 初始值计算成本高时使用惰性初始化
const [data, setData] = useState(() => computeExpensiveInitialValue());
// 关联状态分组管理
const [form, setForm] = useState({ name: '', email: '', role: 'user' });useEffect
useEffect
Dependency Array Rules
依赖数组规则
- Include ALL values from component scope that change over time
- Functions inside effect should be defined inside effect or wrapped in useCallback
- Never lie about dependencies (ESLint: )
react-hooks/exhaustive-deps
- 包含所有组件作用域中随时间变化的变量
- effect内部的函数应该定义在effect内部,或者用useCallback包裹
- 永远不要隐瞒依赖(ESLint规则:)
react-hooks/exhaustive-deps
Cleanup Pattern
清理模式
typescript
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]);typescript
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]);When NOT to Use useEffect
不该使用useEffect的场景
| Instead of useEffect for... | Use This |
|---|---|
| Data fetching | React Query, SWR, or Server Components |
| Transforming data | Compute during render |
| User events | Event handlers |
| Syncing external stores | |
| 不要用useEffect做这些事 | 替代方案 |
|---|---|
| 数据获取 | React Query、SWR 或 Server Components |
| 数据转换 | 渲染期间计算 |
| 用户事件处理 | 事件处理函数 |
| 同步外部存储 | |
Custom Hooks Rules
自定义Hook规则
- Name starts with
use - Encapsulate reusable stateful logic
- One hook per concern
- Return object (not array) for > 2 values
typescript
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;
}- 名称以开头
use - 封装可复用的有状态逻辑
- 每个Hook只负责一个功能
- 返回值超过2个时返回对象(而非数组)
typescript
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;
}Component Composition Patterns
组件组合模式
Compound Components
复合组件
typescript
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>;
};typescript
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>;
};Composition Decision Table
组合模式选型表
| Pattern | Use When | Example |
|---|---|---|
| Compound Components | Related components sharing implicit state | Tabs, Accordion, Menu |
| Slots (Children) | Complex content layout | Card with Header/Body/Footer |
| Render Props | Child needs parent data for flexible rendering | DataFetcher with custom render |
| Higher-Order Component | Cross-cutting concerns (legacy) | withAuth, withTheme |
| Custom Hook | Reusable stateful logic without UI | useDebounce, useLocalStorage |
| 模式 | 适用场景 | 示例 |
|---|---|---|
| 复合组件 | 共享隐式状态的关联组件 | 标签页、折叠面板、菜单 |
| 插槽(Children) | 复杂内容布局 | 带头部/内容/底部的卡片 |
| 渲染属性 | 子组件需要父组件数据实现灵活渲染 | 支持自定义渲染的DataFetcher |
| 高阶组件 | 横切关注点(老旧方案) | withAuth、withTheme |
| 自定义Hook | 无UI的可复用有状态逻辑 | useDebounce、useLocalStorage |
Slots Pattern
插槽模式
typescript
// 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>typescript
// 复杂内容优先使用组合而非props传递
// 不好的写法
<Card title="Hello" subtitle="World" icon={<Star />} actions={<Button>Edit</Button>} />
// 推荐写法
<Card>
<Card.Header>
<Card.Icon><Star /></Card.Icon>
<Card.Title>Hello</Card.Title>
</Card.Header>
<Card.Actions>
<Button>Edit</Button>
</Card.Actions>
</Card>Error Boundaries
错误边界
Placement Strategy Decision Table
放置策略选型表
| Level | Purpose | Example |
|---|---|---|
| Route level | Catch page-level crashes | |
| Feature level | Isolate feature failures | Wrap each major section |
| Data level | Wrap async data components | Around Suspense boundaries |
| Never leaf level | Too granular, adds noise | Do not wrap individual buttons |
| 层级 | 用途 | 示例 |
|---|---|---|
| 路由层级 | 捕获页面级崩溃 | Next.js中的 |
| 功能模块层级 | 隔离功能故障 | 包裹每个主要功能区块 |
| 数据层级 | 包裹异步数据组件 | Suspense边界外层 |
| 永远不要用在叶子节点层级 | 粒度过细,增加冗余 | 不要包裹单个按钮 |
Suspense
Suspense
typescript
// Nested Suspense for granular loading
<Suspense fallback={<PageSkeleton />}>
<Header />
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
<Suspense fallback={<ContentSkeleton />}>
<MainContent />
</Suspense>
</Suspense>typescript
// 嵌套Suspense实现细粒度加载
<Suspense fallback={<PageSkeleton />}>
<Header />
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
<Suspense fallback={<ContentSkeleton />}>
<MainContent />
</Suspense>
</Suspense>Context Optimization
Context优化
Problem: Context causes unnecessary re-renders
问题:Context会导致不必要的重渲染
Solution Decision Table
解决方案选型表
| Technique | Use When | Example |
|---|---|---|
| Split contexts by frequency | Some values update often, some rarely | ThemeContext (rare) vs UIStateContext (frequent) |
| Memoize context value | Provider re-renders with same data | |
| Use selectors (Zustand/Jotai) | Need fine-grained subscriptions | |
| Lift state up | Only parent needs to re-render | Pass data as props to memoized children |
| 技术方案 | 适用场景 | 示例 |
|---|---|---|
| 按更新频率拆分Context | 部分值更新频繁,部分很少更新 | ThemeContext(低频) vs UIStateContext(高频) |
| 记忆化Context值 | Provider使用相同数据重渲染时 | |
| 使用选择器(Zustand/Jotai) | 需要细粒度订阅时 | |
| 状态上移 | 仅父组件需要重渲染时 | 将数据作为props传递给记忆化的子组件 |
Rendering Optimization
渲染优化
Memoization Decision Table
记忆化选型表
| Technique | Use When | Do NOT Use When |
|---|---|---|
| Renders often with same props AND re-render is expensive | Props change every render |
| Expensive computation OR referential equality for deps | Simple calculations |
| Stable function ref for memoized children | Function not passed as prop |
| None (default) | Always start here | Premature optimization |
Rule: Profile BEFORE memoizing. Premature memoization is the most common React anti-pattern.
| 技术方案 | 适用场景 | 禁止使用场景 |
|---|---|---|
| 频繁使用相同props渲染且重渲染成本高 | 每次渲染props都会变化 |
| 计算成本高或者依赖需要引用相等时 | 简单计算场景 |
| 需要给记忆化子组件传递稳定的函数引用时 | 函数不会作为props传递 |
| 不使用(默认) | 所有场景优先选择 | 过早优化 |
规则: 记忆化前先做性能分析。过早记忆化是最常见的React反模式。
Virtualization
虚拟化
For lists > 100 items:
typescript
import { useVirtualizer } from '@tanstack/react-virtual';超过100项的列表使用:
typescript
import { useVirtualizer } from '@tanstack/react-virtual';Server Component Rules
Server Component规则
- Cannot use hooks
- Cannot use browser APIs
- Cannot pass functions as props to Client Components
- CAN import and render Client Components
- CAN pass serializable data to Client Components
typescript
// 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>;
}- 不能使用hooks
- 不能使用浏览器API
- 不能将函数作为props传递给Client Components
- 可以导入并渲染Client Components
- 可以将可序列化数据传递给Client Components
typescript
// Server Component —— 直接获取数据
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子组件 */}
</div>
);
}
// Client Component —— 处理交互
'use client';
function UserActions({ userId }: { userId: string }) {
const [isFollowing, setIsFollowing] = useState(false);
return <Button onClick={() => toggleFollow(userId)}>Follow</Button>;
}Anti-Patterns / Common Mistakes
反模式/常见错误
| Anti-Pattern | Why It Is Wrong | Correct Approach |
|---|---|---|
| Race conditions, no cache, no dedup | React Query or Server Components |
| Prop drilling > 2 levels | Tight coupling, maintenance pain | Composition, context, or Zustand |
| Storing derived state | State that can be computed is unnecessary state | Compute during render |
| Unnecessary effect, stale closures | Derive during render or use key prop |
| Monolithic components (> 200 lines) | Hard to read, test, maintain | Extract sub-components |
| Index as key for dynamic lists | Incorrect reconciliation, stale state | Stable unique ID |
| Direct DOM manipulation | Bypasses React reconciliation | Use refs sparingly, prefer state |
| Testing state values directly | Implementation detail, breaks on refactor | Test user-visible outcomes |
| Memoizing everything | Adds complexity, often slower | Profile first, optimize second |
| 反模式 | 错误原因 | 正确方案 |
|---|---|---|
用 | 存在竞态条件、无缓存、无去重 | React Query或Server Components |
| 超过2层的Props透传 | 耦合度高、维护困难 | 组合、Context或Zustand |
| 存储派生状态 | 可计算得到的状态属于冗余状态 | 渲染期间计算 |
用 | 不必要的effect、闭包过期问题 | 渲染期间派生或使用key属性 |
| 单体组件(超过200行) | 难以阅读、测试、维护 | 拆分提取子组件 |
| 动态列表用索引作为key | 协调错误、状态过期 | 使用稳定的唯一ID |
| 直接操作DOM | 绕过React协调机制 | 尽量少用ref,优先使用状态 |
| 直接测试状态值 | 属于实现细节,重构时容易失效 | 测试用户可见的输出结果 |
| 所有内容都加记忆化 | 增加复杂度,通常反而更慢 | 先做性能分析,再优化 |
Documentation Lookup (Context7)
文档查询(Context7)
Use then for up-to-date docs. Returned docs override memorized knowledge.
mcp__context7__resolve-library-idmcp__context7__query-docs- — for hooks, context, suspense, server components, or React 19+ changes
react - — for App Router patterns, data fetching, or server actions
next.js
先使用再调用获取最新文档。返回的文档优先级高于记忆化知识。
mcp__context7__resolve-library-idmcp__context7__query-docs- —— 查询hooks、context、suspense、server components或React 19+版本变更
react - —— 查询App Router模式、数据获取或服务端动作
next.js
Integration Points
集成点
| Skill | Relationship |
|---|---|
| Frontend skill uses React patterns from this skill |
| React testing follows the strategy pyramid |
| Component code follows clean code principles |
| React rendering optimization follows measurement methodology |
| E2E tests validate React component behavior |
| Review checks for React anti-patterns |
| UI acceptance criteria drive component tests |
| 技能 | 关联关系 |
|---|---|
| 前端技能使用本技能提供的React模式 |
| React测试遵循测试金字塔策略 |
| 组件代码遵循整洁代码原则 |
| React渲染优化遵循性能测量方法论 |
| E2E测试验证React组件行为 |
| 代码评审检查React反模式 |
| UI验收标准驱动组件测试 |
Skill Type
技能类型
FLEXIBLE — Apply these patterns based on the specific React version, project structure, and team conventions. The principles are consistent, but implementation details may vary. Always profile before optimizing.
灵活适配 —— 根据具体的React版本、项目结构和团队约定应用这些模式。原则是统一的,但实现细节可能有所不同。优化前务必先做性能分析。