react-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React 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

操作步骤

  1. Identify the component's single responsibility
  2. Determine data requirements (server vs client data)
  3. Choose Server Component (default) or Client Component
  4. Identify state management needs
  5. Plan error and loading states
  1. 明确组件的单一职责
  2. 确定数据需求(服务端数据/客户端数据)
  3. 选择Server Component(默认)或Client Component
  4. 识别状态管理需求
  5. 规划错误和加载状态

Server vs Client Decision Table

服务端vs客户端组件选型表

NeedComponent TypeReason
Direct data fetching (DB, API)Server (default)No client JS, faster
Event handlers (onClick, onChange)Client (
'use client'
)
Needs browser interactivity
useState / useReducerClientState requires client runtime
useEffect / useLayoutEffectClientSide effects require client
Browser APIs (window, localStorage)ClientServer has no browser
Third-party libs using client featuresClientLibrary requires client
No interactivity neededServer (default)Smaller bundle, faster
需求组件类型原因
直接获取数据(DB、API)Server(默认)无客户端JS,速度更快
事件处理(onClick、onChange)Client(
'use client'
需要浏览器交互能力
useState / useReducerClient状态需要客户端运行时支持
useEffect / useLayoutEffectClient副作用需要客户端环境
浏览器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

操作步骤

  1. Apply appropriate composition pattern
  2. Implement hooks correctly
  3. Add error boundaries and Suspense
  4. Optimize rendering where profiling shows need
  5. Write tests that verify behavior
  1. 应用合适的组合模式
  2. 正确实现hooks
  3. 添加错误边界和Suspense
  4. 在 Profiler 显示有需要的地方优化渲染
  5. 编写验证组件行为的测试用例

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

操作步骤

  1. Write tests using accessible queries
  2. Test user interactions and outcomes
  3. Test error and loading states
  4. Verify accessibility
  1. 使用可访问查询编写测试
  2. 测试用户交互和输出结果
  3. 测试错误和加载状态
  4. 验证可访问性

Query Priority (React Testing Library)

查询优先级(React Testing Library)

PriorityQueryUse For
1st
getByRole
Any element with ARIA role
2nd
getByLabelText
Form fields
3rd
getByPlaceholderText
Fields without labels
4th
getByText
Non-interactive elements
Last
getByTestId
When nothing else works
优先级查询方法适用场景
1st
getByRole
所有带ARIA角色的元素
2nd
getByLabelText
表单字段
3rd
getByPlaceholderText
无标签的输入字段
4th
getByText
非交互元素
最后
getByTestId
其他查询都不适用的场景

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 fetchingReact Query, SWR, or Server Components
Transforming dataCompute during render
User eventsEvent handlers
Syncing external stores
useSyncExternalStore
不要用useEffect做这些事替代方案
数据获取React Query、SWR 或 Server Components
数据转换渲染期间计算
用户事件处理事件处理函数
同步外部存储
useSyncExternalStore

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

组合模式选型表

PatternUse WhenExample
Compound ComponentsRelated components sharing implicit stateTabs, Accordion, Menu
Slots (Children)Complex content layoutCard with Header/Body/Footer
Render PropsChild needs parent data for flexible renderingDataFetcher with custom render
Higher-Order ComponentCross-cutting concerns (legacy)withAuth, withTheme
Custom HookReusable stateful logic without UIuseDebounce, 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

放置策略选型表

LevelPurposeExample
Route levelCatch page-level crashes
error.tsx
in Next.js
Feature levelIsolate feature failuresWrap each major section
Data levelWrap async data componentsAround Suspense boundaries
Never leaf levelToo granular, adds noiseDo not wrap individual buttons

层级用途示例
路由层级捕获页面级崩溃Next.js中的
error.tsx
功能模块层级隔离功能故障包裹每个主要功能区块
数据层级包裹异步数据组件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

解决方案选型表

TechniqueUse WhenExample
Split contexts by frequencySome values update often, some rarelyThemeContext (rare) vs UIStateContext (frequent)
Memoize context valueProvider re-renders with same data
useMemo(() => ({ state, dispatch }), [state])
Use selectors (Zustand/Jotai)Need fine-grained subscriptions
useStore(state => state.user.name)
Lift state upOnly parent needs to re-renderPass data as props to memoized children

技术方案适用场景示例
按更新频率拆分Context部分值更新频繁,部分很少更新ThemeContext(低频) vs UIStateContext(高频)
记忆化Context值Provider使用相同数据重渲染时
useMemo(() => ({ state, dispatch }), [state])
使用选择器(Zustand/Jotai)需要细粒度订阅时
useStore(state => state.user.name)
状态上移仅父组件需要重渲染时将数据作为props传递给记忆化的子组件

Rendering Optimization

渲染优化

Memoization Decision Table

记忆化选型表

TechniqueUse WhenDo NOT Use When
React.memo
Renders often with same props AND re-render is expensiveProps change every render
useMemo
Expensive computation OR referential equality for depsSimple calculations
useCallback
Stable function ref for memoized childrenFunction not passed as prop
None (default)Always start herePremature optimization
Rule: Profile BEFORE memoizing. Premature memoization is the most common React anti-pattern.
技术方案适用场景禁止使用场景
React.memo
频繁使用相同props渲染且重渲染成本高每次渲染props都会变化
useMemo
计算成本高或者依赖需要引用相等时简单计算场景
useCallback
需要给记忆化子组件传递稳定的函数引用时函数不会作为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-PatternWhy It Is WrongCorrect Approach
useEffect
for data fetching
Race conditions, no cache, no dedupReact Query or Server Components
Prop drilling > 2 levelsTight coupling, maintenance painComposition, context, or Zustand
Storing derived stateState that can be computed is unnecessary stateCompute during render
useEffect
to sync state from props
Unnecessary effect, stale closuresDerive during render or use key prop
Monolithic components (> 200 lines)Hard to read, test, maintainExtract sub-components
Index as key for dynamic listsIncorrect reconciliation, stale stateStable unique ID
Direct DOM manipulationBypasses React reconciliationUse refs sparingly, prefer state
Testing state values directlyImplementation detail, breaks on refactorTest user-visible outcomes
Memoizing everythingAdds complexity, often slowerProfile first, optimize second

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

Documentation Lookup (Context7)

文档查询(Context7)

Use
mcp__context7__resolve-library-id
then
mcp__context7__query-docs
for up-to-date docs. Returned docs override memorized knowledge.
  • react
    — for hooks, context, suspense, server components, or React 19+ changes
  • next.js
    — for App Router patterns, data fetching, or server actions

先使用
mcp__context7__resolve-library-id
再调用
mcp__context7__query-docs
获取最新文档。返回的文档优先级高于记忆化知识。
  • react
    —— 查询hooks、context、suspense、server components或React 19+版本变更
  • next.js
    —— 查询App Router模式、数据获取或服务端动作

Integration Points

集成点

SkillRelationship
senior-frontend
Frontend skill uses React patterns from this skill
testing-strategy
React testing follows the strategy pyramid
clean-code
Component code follows clean code principles
performance-optimization
React rendering optimization follows measurement methodology
webapp-testing
E2E tests validate React component behavior
code-review
Review checks for React anti-patterns
acceptance-testing
UI acceptance criteria drive component tests

技能关联关系
senior-frontend
前端技能使用本技能提供的React模式
testing-strategy
React测试遵循测试金字塔策略
clean-code
组件代码遵循整洁代码原则
performance-optimization
React渲染优化遵循性能测量方法论
webapp-testing
E2E测试验证React组件行为
code-review
代码评审检查React反模式
acceptance-testing
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版本、项目结构和团队约定应用这些模式。原则是统一的,但实现细节可能有所不同。优化前务必先做性能分析。