跳到主要内容

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-windowreact-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 — 路由/弹窗级切割
  • webpack magic 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 selectoruseStore(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 后同步运行