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
- 打开 DevTools → Profiler
- 点击录制
- 做交互
- 对比提交耗时、渲染次数和组件树,确认真正的热点
勾选 "Record why each component rendered",可以看到重渲染的原因。
总结
| 优化手段 | 适用场景 |
|---|
| State 下移 | 某个 State 变化导致大片组件重渲染 |
| React.memo | 子组件 props 不常变 |
| useCallback/useMemo | 配合 memo 使用,防止 props 变 |
| 拆分 Context | 某个值变化导致无关组件重渲染 |
| 虚拟列表 | 长列表(千条以上) |
| React.lazy | 首屏不用的组件 |
记住:优化之前先 Profiler,找到瓶颈再动手,别凭感觉瞎优化。
一条实用排查顺序
先确认是否存在请求瀑布或首屏包过大,再看组件是否渲染了过多内容,最后才处理 memo、缓存和 Context 拆分。优化后重新录制并比较指标,避免“代码更复杂了,但用户没有感知到更快”。