React 性能优化实战
性能优化全景
1. 渲染优化
避免不必要的重渲染
React.memo(Component, areEqual)— 对比 props,默认浅比较- 父组件回调用
useCallback稳定引用,防止子组件因回调引用变化重渲染 - 将「变化的状态」下沉到最小作用域的子组件,减少影响范围
拆分组件粒度
// ❌ 状态变化导致整个列表重渲染
function Page() {
const [count, setCount] = useState(0)
return (
<>
<button onClick={() => setCount(c => c + 1)}>{count}</button>
<HeavyList /> {/* 每次都重渲染 */}
</>
)
}
// ✅ 将 count 状态隔离
function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
function Page() {
return (<><Counter /><HeavyList /></>)
}
2. 列表虚拟化
- 数据量 > 500 条时使用
react-window或react-virtual - 只渲染可视区域内的元素,降低 DOM 节点数
- 配合
overscanCount预渲染边缘元素,避免滚动白屏
3. 代码分割与懒加载
// 路由级分割
const Dashboard = React.lazy(() => import('./Dashboard'))
function App() {
return (
<Suspense fallback={<Skeleton />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
)
}
import()动态导入 +React.lazy— 路由/弹窗级切割webpackmagic comment:/* webpackChunkName: "xxx" */命名 chunk- 预加载:鼠标 hover 时触发
import()
4. 并发特性(React 18)
| API | 作用 |
|---|---|
useTransition | 将状态更新标记为「非紧急」,保持 UI 响应 |
useDeferredValue | 延迟某个值的更新,类似防抖 |
startTransition | 在事件处理器外触发低优先级更新 |
const [isPending, startTransition] = useTransition()
startTransition(() => {
setSearchQuery(value) // 低优先级,不阻塞输入
})
5. 状态管理性能
- Context 拆分:避免把所有状态放一个 Context,按功能拆分降低订阅范围
- Context + useMemo:value 对象需 memoize,否则每次渲染都是新引用
- Zustand selector:
useStore(state => state.count)只订阅用到的切片 - Jotai 原子化:精确更新,只有依赖该 atom 的组件重渲染
6. 工具与度量
- React DevTools Profiler — 录制渲染时间,找到耗时组件
- Why Did You Render — 检测不必要的重渲染并输出原因
- Lighthouse — 页面整体性能分
- Web Vitals — LCP / FID / CLS 核心指标监控
7. 高频面试题
- memo 一定有收益吗? 浅比较本身有成本,仅对渲染耗时高的组件使用
- 如何定位慢渲染? Profiler 找 commit 时长 > 16ms 的组件,再缩小范围
- Suspense 与错误边界的关系? Suspense 处理加载态,ErrorBoundary 处理错误态,两者搭配
- 并发渲染会破坏 useEffect 顺序吗? 不会,effect 仍在 commit 后同步运行