React 性能优化指南
别瞎优化,先用 DevTools 找到瓶颈
核心思路
React 性能问题就一个核心:减少不必要的重渲染。
1. 避免不必要的重渲染
把 State 往下移
如果父组件有个频繁变化的 State,但只有一小块 UI 用到了它,就把这块 UI 拆成子组件:
// ❌ 父组件任何 State 变化,子组件都会重渲染
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<ExpensiveComponent /> // 用的不是 count,但也跟着重渲染
<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 没变就跳过渲染。
2. 保持引用稳定
如果用了 memo,但 props 里有函数或对象,还得保证它们不变:
useCallback — 缓存函数
const handleClick = useCallback(() => {
console.log(count);
}, [count]); // 只有 count 变才创建新函数
useMemo — 缓存对象/计算结果
const config = useMemo(
() => ({
theme: "dark",
language: "zh",
}),
[],
); // 依赖空数组,永远返回同一个对象
3. 列表优化
Key 要用唯一 ID
详见key 到底有什么用?。
虚拟列表
数据几千条?不要全渲染。用 react-window 或 react-virtualized,只渲染可视区域那十几条。
4. Context 优化
Context 的 value 变了,所有消费者都会重渲染。
拆分成多个 Context
// ❌ 什么都在一个 Context 里
const AppContext = createContext();
// ✅ 按业务拆分
const ThemeContext = createContext();
const UserContext = createContext();
value 用 useMemo 缓存
<AppContext.Provider value={useMemo(() => ({
user,
setUser
}), [user])}>
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,找到瓶颈再动手,别凭感觉瞎优化。