React 性能优化指南

别瞎优化,先用 DevTools 找到瓶颈

核心思路

React 性能优化不只是在组件上加 memo:先定位瓶颈,再分别处理不必要的重渲染、过大的渲染范围、过长的任务和过大的首屏包体。

1. 避免不必要的重渲染

把 State 往下移

如果父组件有个频繁变化的 State,但只有一小块 UI 用到了它,就把这块 UI 拆成子组件:

// ❌ 如果 ExpensiveComponent 依赖父组件每次新建的 props,
//    父组件更新时它也可能跟着重渲染
function Parent() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <ExpensiveComponent />
      <button onClick={() => setCount((c) => c + 1)}>{count}</button>
    </div>
  );
}

// ✅ 把频繁变化的 State 放到真正使用它的组件附近
function Parent() {
  return (
    <div>
      <ExpensiveComponent />
      <Counter />
    </div>
  );
}

function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}

React.memo(有成本,按需使用)

对不常变化的子组件用 React.memo

// 只有 props 变了才会重渲染
const MyComponent = React.memo(function MyComponent({ name }) {
  return <div>{name}</div>;
});

memo 做的是浅比较,但比较本身也有成本;只有组件渲染开销明显、且 props 经常保持稳定时才值得使用。它也不能阻止组件读取的 Context 变化带来的更新。

2. 保持引用稳定

如果用了 memo,函数、对象、数组等非原始值每次渲染都可能产生新引用。只有这些引用变化确实导致昂贵子树重渲染时,才需要稳定它们:

useCallback — 缓存函数

const handleClick = useCallback(() => {
  console.log(count);
}, [count]); // 只有 count 变才创建新函数

useMemo — 缓存对象/计算结果

const config = useMemo(
  () => ({
    theme: "dark",
    language: "zh",
  }),
  [],
); // 仅当确实需要稳定引用时使用

3. 列表优化

Key 要用唯一 ID

详见虚拟 DOM 与 Diff 算法中的 Key 说明。

虚拟列表

数据量很大、且列表项渲染成本高时,再考虑 react-window 等虚拟列表;它会增加测量、滚动和可访问性处理的复杂度,并不是所有列表都应该默认使用。

4. Context 优化

Provider 的 value 引用变化会让使用该 Context 的消费者重新检查;即使消费者只读取其中一个字段,也可能被同一个 Context 的其他字段变化牵连。

拆分成多个 Context

// ❌ 什么都在一个 Context 里
const AppContext = createContext();

// ✅ 按业务拆分
const ThemeContext = createContext();
const UserContext = createContext();

value 用 useMemo 缓存

const value = useMemo(() => ({ user, setUser }), [user]);

<AppContext.Provider value={value}>

5. 懒加载

首屏不用的组件,延迟加载:

const HeavyChart = React.lazy(() => import("./HeavyChart"));

function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <HeavyChart />
    </Suspense>
  );
}

怎么知道哪里慢?

React DevTools Profiler

  1. 打开 DevTools → Profiler
  2. 点击录制
  3. 做交互
  4. 对比提交耗时、渲染次数和组件树,确认真正的热点

勾选 "Record why each component rendered",可以看到重渲染的原因。

总结

优化手段适用场景
State 下移某个 State 变化导致大片组件重渲染
React.memo子组件 props 不常变
useCallback/useMemo配合 memo 使用,防止 props 变
拆分 Context某个值变化导致无关组件重渲染
虚拟列表长列表(千条以上)
React.lazy首屏不用的组件

记住:优化之前先 Profiler,找到瓶颈再动手,别凭感觉瞎优化。

一条实用排查顺序

先确认是否存在请求瀑布或首屏包过大,再看组件是否渲染了过多内容,最后才处理 memo、缓存和 Context 拆分。优化后重新录制并比较指标,避免“代码更复杂了,但用户没有感知到更快”。