component-optimization

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Component Optimization

组件优化

Master React performance optimization techniques. Learn how to identify and fix performance bottlenecks, prevent unnecessary re-renders, and create fast, responsive UIs.
掌握React性能优化技巧。学习如何识别和修复性能瓶颈,避免不必要的重渲染,打造快速、响应式的UI。

Quick Reference

快速参考

Prevent Re-renders with React.memo

使用React.memo避免重渲染

typescript
const MemoizedComponent = React.memo(function Component({ data }) {
  return <div>{data}</div>;
});
typescript
const MemoizedComponent = React.memo(function Component({ data }) {
  return <div>{data}</div>;
});

Memoize Expensive Calculations

缓存昂贵计算结果

typescript
const result = useMemo(() => 
  expensiveCalculation(data), 
  [data]
);
typescript
const result = useMemo(() => 
  expensiveCalculation(data), 
  [data]
);

Memoize Callback Functions

缓存回调函数

typescript
const handleClick = useCallback(() => {
  doSomething(value);
}, [value]);
typescript
const handleClick = useCallback(() => {
  doSomething(value);
}, [value]);

Code Splitting

代码分割

typescript
const LazyComponent = lazy(() => import('./Component'));

<Suspense fallback={<Spinner />}>
  <LazyComponent />
</Suspense>
typescript
const LazyComponent = lazy(() => import('./Component'));

<Suspense fallback={<Spinner />}>
  <LazyComponent />
</Suspense>

When to Use This Skill

何时使用这些技巧

  • Component renders slowly or causes UI lag
  • Large lists or tables perform poorly
  • Callbacks cause child components to re-render
  • Initial page load is too slow
  • Bundle size is too large
  • Profiler shows unnecessary re-renders
  • 组件渲染缓慢或导致UI卡顿
  • 大型列表或表格性能不佳
  • 回调函数导致子组件重渲染
  • 初始页面加载过慢
  • 包体积过大
  • 性能分析器显示存在不必要的重渲染

Understanding Re-renders

理解重渲染

When Does React Re-render?

React何时会重渲染?

React re-renders a component when:
  1. State changes -
    useState
    or
    useReducer
    updates
  2. Props change - Parent passes new props
  3. Parent re-renders - By default, all children re-render
  4. Context changes - Any context value updates
当以下情况发生时,React会重渲染组件:
  1. 状态变更 -
    useState
    useReducer
    更新
  2. Props变更 - 父组件传递新的props
  3. 父组件重渲染 - 默认情况下,所有子组件都会重渲染
  4. Context变更 - 任何Context值更新

The Re-render Problem

重渲染问题

tsx
// ❌ PROBLEM: Child re-renders unnecessarily
function Parent() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      <ExpensiveChild /> {/* Re-renders every time! */}
    </div>
  );
}
Even though
ExpensiveChild
doesn't use
count
, it re-renders whenever the parent does.
tsx
// ❌ 问题:子组件不必要地重渲染
function Parent() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      <ExpensiveChild /> {/* 每次都会重渲染! */}
    </div>
  );
}
即使
ExpensiveChild
不使用
count
,它也会在父组件重渲染时跟着重渲染。

Optimization Techniques

优化技巧

1. React.memo - Prevent Unnecessary Re-renders

1. React.memo - 避免不必要的重渲染

React.memo
skips re-rendering if props haven't changed.
tsx
// ✅ SOLUTION: Memoize the child
const ExpensiveChild = React.memo(function ExpensiveChild() {
  console.log("ExpensiveChild rendered");
  
  return (
    <div>
      {/* Expensive rendering logic */}
    </div>
  );
});

function Parent() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      <ExpensiveChild /> {/* No longer re-renders! */}
    </div>
  );
}
When to use:
  • Component renders often but props rarely change
  • Component has expensive rendering logic
  • Component is large with many children
When NOT to use:
  • Props change frequently
  • Component is simple and fast
  • Premature optimization
React.memo
会在props未变更时跳过重渲染。
tsx
// ✅ 解决方案:缓存子组件
const ExpensiveChild = React.memo(function ExpensiveChild() {
  console.log("ExpensiveChild rendered");
  
  return (
    <div>
      {/* 昂贵的渲染逻辑 */}
    </div>
  );
});

function Parent() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      <ExpensiveChild /> {/* 不再重渲染! */}
    </div>
  );
}
适用场景:
  • 组件频繁渲染但props很少变更
  • 组件包含昂贵的渲染逻辑
  • 组件较大且包含多个子组件
不适用场景:
  • props频繁变更
  • 组件简单且渲染快速
  • 过早优化

2. useMemo - Memoize Expensive Calculations

2. useMemo - 缓存昂贵计算结果

useMemo
caches calculation results between renders.
tsx
function ProductList({ products, filters }) {
  // ❌ Recalculates on every render
  const filtered = products.filter(p => 
    p.category === filters.category
  );
  
  // ✅ Only recalculates when dependencies change
  const filtered = useMemo(
    () => products.filter(p => p.category === filters.category),
    [products, filters.category]
  );
  
  return (
    <ul>
      {filtered.map(product => (
        <li key={product.id}>{product.name}</li>
      ))}
    </ul>
  );
}
When to use:
  • Expensive calculations (filtering, sorting, mapping large arrays)
  • Derived state from props or state
  • Creating objects/arrays passed as props to memoized children
When NOT to use:
  • Simple calculations (they're fast enough)
  • Values that change every render anyway
  • Premature optimization
useMemo
会在渲染之间缓存计算结果。
tsx
function ProductList({ products, filters }) {
  // ❌ 每次渲染都会重新计算
  const filtered = products.filter(p => 
    p.category === filters.category
  );
  
  // ✅ 仅当依赖项变更时重新计算
  const filtered = useMemo(
    () => products.filter(p => p.category === filters.category),
    [products, filters.category]
  );
  
  return (
    <ul>
      {filtered.map(product => (
        <li key={product.id}>{product.name}</li>
      ))}
    </ul>
  );
}
适用场景:
  • 昂贵的计算(过滤、排序、映射大型数组)
  • 从props或state派生的状态
  • 创建作为props传递给已缓存子组件的对象/数组
不适用场景:
  • 简单计算(速度足够快)
  • 每次渲染都会变更的值
  • 过早优化

3. useCallback - Memoize Functions

3. useCallback - 缓存函数

useCallback
prevents creating new function instances on every render.
tsx
function TodoList({ todos }) {
  const [filter, setFilter] = useState("all");
  
  // ❌ New function every render, breaks React.memo
  const handleComplete = (id) => {
    completeTodo(id);
  };
  
  // ✅ Same function reference between renders
  const handleComplete = useCallback((id) => {
    completeTodo(id);
  }, []);
  
  return (
    <ul>
      {todos.map(todo => (
        <MemoizedTodoItem
          key={todo.id}
          todo={todo}
          onComplete={handleComplete}
        />
      ))}
    </ul>
  );
}
When to use:
  • Passing callbacks to memoized children
  • Functions used in dependency arrays
  • Functions passed to many children
When NOT to use:
  • Functions not passed as props
  • Parent component re-renders frequently anyway
  • Simple event handlers
useCallback
避免在每次渲染时创建新的函数实例。
tsx
function TodoList({ todos }) {
  const [filter, setFilter] = useState("all");
  
  // ❌ 每次渲染创建新函数,破坏React.memo的效果
  const handleComplete = (id) => {
    completeTodo(id);
  };
  
  // ✅ 渲染之间保持相同的函数引用
  const handleComplete = useCallback((id) => {
    completeTodo(id);
  }, []);
  
  return (
    <ul>
      {todos.map(todo => (
        <MemoizedTodoItem
          key={todo.id}
          todo={todo}
          onComplete={handleComplete}
        />
      ))}
    </ul>
  );
}
适用场景:
  • 向已缓存的子组件传递回调函数
  • 在依赖数组中使用的函数
  • 传递给多个子组件的函数
不适用场景:
  • 未作为props传递的函数
  • 父组件本身频繁重渲染
  • 简单事件处理程序

4. Code Splitting with React.lazy

4. 使用React.lazy进行代码分割

Split large components into separate bundles that load on demand.
tsx
import { lazy, Suspense } from "react";

// ✅ Only loads when needed
const AdminPanel = lazy(() => import("./AdminPanel"));
const Charts = lazy(() => import("./Charts"));

function Dashboard() {
  const [showAdmin, setShowAdmin] = useState(false);
  
  return (
    <div>
      <h1>Dashboard</h1>
      
      {showAdmin && (
        <Suspense fallback={<Spinner />}>
          <AdminPanel />
        </Suspense>
      )}
      
      <Suspense fallback={<ChartsSkeleton />}>
        <Charts />
      </Suspense>
    </div>
  );
}
When to use:
  • Large components not needed initially
  • Admin/authenticated sections
  • Modals and dialogs
  • Route-based code splitting
将大型组件拆分为按需加载的独立包。
tsx
import { lazy, Suspense } from "react";

// ✅ 仅在需要时加载
const AdminPanel = lazy(() => import("./AdminPanel"));
const Charts = lazy(() => import("./Charts"));

function Dashboard() {
  const [showAdmin, setShowAdmin] = useState(false);
  
  return (
    <div>
      <h1>Dashboard</h1>
      
      {showAdmin && (
        <Suspense fallback={<Spinner />}>
          <AdminPanel />
        </Suspense>
      )}
      
      <Suspense fallback={<ChartsSkeleton />}>
        <Charts />
      </Suspense>
    </div>
  );
}
适用场景:
  • 初始加载不需要的大型组件
  • 管理员/认证专属区域
  • 模态框和对话框
  • 基于路由的代码分割

5. Virtualization for Long Lists

5. 长列表虚拟化

Render only visible items in long lists.
tsx
import { useVirtualizer } from "@tanstack/react-virtual";

function VirtualList({ items }) {
  const parentRef = useRef<HTMLDivElement>(null);
  
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
  });
  
  return (
    <div ref={parentRef} style={{ height: "400px", overflow: "auto" }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px` }}>
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <div
            key={virtualItem.key}
            style={{
              position: "absolute",
              top: 0,
              left: 0,
              width: "100%",
              height: `${virtualItem.size}px`,
              transform: `translateY(${virtualItem.start}px)`,
            }}
          >
            {items[virtualItem.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}
When to use:
  • Lists with 100+ items
  • Tables with many rows
  • Infinite scrolling
  • Chat messages or feeds
仅渲染长列表中可见的项。
tsx
import { useVirtualizer } from "@tanstack/react-virtual";

function VirtualList({ items }) {
  const parentRef = useRef<HTMLDivElement>(null);
  
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
  });
  
  return (
    <div ref={parentRef} style={{ height: "400px", overflow: "auto" }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px` }}>
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <div
            key={virtualItem.key}
            style={{
              position: "absolute",
              top: 0,
              left: 0,
              width: "100%",
              height: `${virtualItem.size}px`,
              transform: `translateY(${virtualItem.start}px)`,
            }}
          >
            {items[virtualItem.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}
适用场景:
  • 包含100+项的列表
  • 包含大量行的表格
  • 无限滚动
  • 聊天消息或信息流

Advanced Patterns

高级模式

1. Context Optimization

1. Context优化

Split contexts to prevent unnecessary re-renders:
tsx
// ❌ Everything re-renders when anything changes
const AppContext = createContext({
  user: null,
  theme: "light",
  settings: {},
});

// ✅ Separate contexts for independent data
const UserContext = createContext(null);
const ThemeContext = createContext("light");
const SettingsContext = createContext({});

function App() {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState("light");
  const [settings, setSettings] = useState({});
  
  return (
    <UserContext.Provider value={user}>
      <ThemeContext.Provider value={theme}>
        <SettingsContext.Provider value={settings}>
          <AppContent />
        </SettingsContext.Provider>
      </ThemeContext.Provider>
    </UserContext.Provider>
  );
}
拆分Context以避免不必要的重渲染:
tsx
// ❌ 任何值变更时所有组件都会重渲染
const AppContext = createContext({
  user: null,
  theme: "light",
  settings: {},
});

// ✅ 为独立数据拆分Context
const UserContext = createContext(null);
const ThemeContext = createContext("light");
const SettingsContext = createContext({});

function App() {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState("light");
  const [settings, setSettings] = useState({});
  
  return (
    <UserContext.Provider value={user}>
      <ThemeContext.Provider value={theme}>
        <SettingsContext.Provider value={settings}>
          <AppContent />
        </SettingsContext.Provider>
      </ThemeContext.Provider>
    </UserContext.Provider>
  );
}

2. Component Composition vs Props

2. 组件组合 vs Props

Use children instead of passing components as props:
tsx
// ❌ Slower prop creates new component
function Parent({ content }) {
  const [state, setState] = useState(0);
  return <div>{content}</div>;
}

<Parent content={<ExpensiveComponent />} />

// ✅ Faster - children don't re-render
function Parent({ children }) {
  const [state, setState] = useState(0);
  return <div>{children}</div>;
}

<Parent>
  <ExpensiveComponent />
</Parent>
使用children而非传递组件作为props:
tsx
// ❌ 较慢——props会创建新组件
function Parent({ content }) {
  const [state, setState] = useState(0);
  return <div>{content}</div>;
}

<Parent content={<ExpensiveComponent />} />

// ✅ 更快——children不会重渲染
function Parent({ children }) {
  const [state, setState] = useState(0);
  return <div>{children}</div>;
}

<Parent>
  <ExpensiveComponent />
</Parent>

3. State Colocation

3. 状态就近原则

Move state closer to where it's used:
tsx
// ❌ Top-level state causes full tree re-render
function App() {
  const [color, setColor] = useState("blue");
  
  return (
    <div>
      <ColorPicker color={color} onChange={setColor} />
      <ExpensiveComponent />
      <AnotherExpensiveComponent />
    </div>
  );
}

// ✅ Isolated state only affects relevant components
function App() {
  return (
    <div>
      <ColorPickerSection />
      <ExpensiveComponent />
      <AnotherExpensiveComponent />
    </div>
  );
}

function ColorPickerSection() {
  const [color, setColor] = useState("blue");
  return <ColorPicker color={color} onChange={setColor} />;
}
将状态移至使用它的组件附近:
tsx
// ❌ 顶层状态导致整个组件树重渲染
function App() {
  const [color, setColor] = useState("blue");
  
  return (
    <div>
      <ColorPicker color={color} onChange={setColor} />
      <ExpensiveComponent />
      <AnotherExpensiveComponent />
    </div>
  );
}

// ✅ 隔离状态仅影响相关组件
function App() {
  return (
    <div>
      <ColorPickerSection />
      <ExpensiveComponent />
      <AnotherExpensiveComponent />
    </div>
  );
}

function ColorPickerSection() {
  const [color, setColor] = useState("blue");
  return <ColorPicker color={color} onChange={setColor} />;
}

Profiling Performance

性能分析

1. React DevTools Profiler

1. React DevTools性能分析器

tsx
// Add Profiler to measure render times
import { Profiler } from "react";

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <AppContent />
    </Profiler>
  );
}

function onRenderCallback(
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime
) {
  console.log(`${id} (${phase}) took ${actualDuration}ms`);
}
tsx
// 添加Profiler测量渲染时间
import { Profiler } from "react";

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <AppContent />
    </Profiler>
  );
}

function onRenderCallback(
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime
) {
  console.log(`${id}${phase})耗时 ${actualDuration}ms`);
}

2. Performance Timeline

2. 性能时间线

tsx
// Mark performance in browser DevTools
function expensiveOperation() {
  performance.mark("expensive-start");
  
  // ... expensive work ...
  
  performance.mark("expensive-end");
  performance.measure(
    "expensive-operation",
    "expensive-start",
    "expensive-end"
  );
}
tsx
// 在浏览器DevTools中标记性能
function expensiveOperation() {
  performance.mark("expensive-start");
  
  // ... 耗时操作 ...
  
  performance.mark("expensive-end");
  performance.measure(
    "expensive-operation",
    "expensive-start",
    "expensive-end"
  );
}

Common Issues

常见问题

Issue 1: React.memo Not Working

问题1:React.memo未生效

Symptoms: Memoized component still re-renders Cause: Props include objects/arrays/functions that change reference Solution: Memoize prop values
tsx
// ❌ New object every render
<MemoizedChild config={{ theme: "dark" }} />

// ✅ Stable reference
const config = useMemo(() => ({ theme: "dark" }), []);
<MemoizedChild config={config} />

// ✅ Or use individual props
<MemoizedChild theme="dark" />
症状:已缓存的组件仍在重渲染 原因:Props包含引用变更的对象/数组/函数 解决方案:缓存prop值
tsx
// ❌ 每次渲染创建新对象
<MemoizedChild config={{ theme: "dark" }} />

// ✅ 稳定引用
const config = useMemo(() => ({ theme: "dark" }), []);
<MemoizedChild config={config} />

// ✅ 或者使用单独的props
<MemoizedChild theme="dark" />

Issue 2: Over-Optimization

问题2:过度优化

Symptoms: Code is complex but not faster Cause: Memoization overhead exceeds benefit Solution: Remove unnecessary optimization
tsx
// ❌ Overkill - simple operations are fast
const result = useMemo(() => a + b, [a, b]);

// ✅ Just calculate it
const result = a + b;
症状:代码复杂但并未变快 原因:缓存的开销超过了收益 解决方案:移除不必要的优化
tsx
// ❌ 小题大做——简单操作速度足够快
const result = useMemo(() => a + b, [a, b]);

// ✅ 直接计算即可
const result = a + b;

Issue 3: Stale Closures

问题3:过时闭包

Symptoms: useCallback uses old values Cause: Missing dependencies Solution: Add all dependencies
tsx
// ❌ Uses stale 'count'
const handleClick = useCallback(() => {
  console.log(count);
}, []);

// ✅ Always current
const handleClick = useCallback(() => {
  console.log(count);
}, [count]);
症状:useCallback使用旧值 原因:缺少依赖项 解决方案:添加所有依赖项
tsx
// ❌ 使用过时的'count'
const handleClick = useCallback(() => {
  console.log(count);
}, []);

// ✅ 始终使用最新值
const handleClick = useCallback(() => {
  console.log(count);
}, [count]);

Best Practices

最佳实践

  • Profile before optimizing (use React DevTools)
  • Start with proper state colocation
  • Use React.memo for expensive components with stable props
  • Use useMemo for expensive calculations, not simple ones
  • Use useCallback for functions passed to memoized children
  • Split large components into smaller ones
  • Use code splitting for large, conditionally rendered components
  • Virtualize long lists (100+ items)
  • Split contexts to reduce re-render scope
  • Measure impact - ensure optimization actually helps
  • 先分析再优化(使用React DevTools)
  • 从合理的状态就近原则开始
  • 为props稳定的昂贵组件使用React.memo
  • 为昂贵计算使用useMemo,而非简单计算
  • 为传递给已缓存子组件的函数使用useCallback
  • 将大型组件拆分为更小的组件
  • 为大型、条件渲染的组件使用代码分割
  • 对长列表(100+项)进行虚拟化
  • 拆分Context以缩小重渲染范围
  • 衡量影响——确保优化确实有效

Anti-Patterns

反模式

Things to avoid:
  • ❌ Memoizing everything by default
  • ❌ Premature optimization without measuring
  • ❌ Using useMemo for simple calculations
  • ❌ Forgetting dependencies in hooks
  • ❌ Creating new objects/arrays in render
  • ❌ Deeply nested component trees
  • ❌ Massive components with too many responsibilities
  • ❌ Global state for local concerns
需要避免的情况:
  • ❌ 默认缓存所有内容
  • ❌ 未进行测量就过早优化
  • ❌ 为简单计算使用useMemo
  • ❌ 忘记钩子中的依赖项
  • ❌ 在渲染中创建新对象/数组
  • ❌ 深度嵌套的组件树
  • ❌ 承担过多职责的巨型组件
  • ❌ 为局部状态使用全局状态

Performance Checklist

性能检查清单

When debugging slow components:
  1. Identify - Use Profiler to find slow components
  2. Measure - Record baseline performance
  3. Analyze - Check why component renders
  4. Optimize - Apply appropriate technique
  5. Verify - Measure improvement
  6. Document - Note why optimization was needed
调试缓慢组件时:
  1. 识别 - 使用性能分析器找到缓慢的组件
  2. 测量 - 记录基准性能
  3. 分析 - 检查组件重渲染的原因
  4. 优化 - 应用合适的技巧
  5. 验证 - 测量优化效果
  6. 记录 - 说明优化的原因

References

参考资料