跳到主要内容

React有哪些性能优化的方法

面试速答(30 秒版 TL;DR)

  • React 性能优化不要背成一串 Hook 名字,更好的答法是 按链路拆层
  • 我通常会分成 6 层:
    • 定位层:先量化瓶颈
    • 组件层:控制重渲染范围
    • 状态层:状态归属和共享边界设计
    • 列表与交互层:长列表、重计算、输入响应优化
    • 资源层:代码分割、懒加载、依赖治理
    • 监控层:上线后持续验证
  • React 专属的关键抓手主要有:
    • 合理拆组件
    • 状态下沉
    • memo / useMemo / useCallback 按需使用
    • 虚拟列表
    • startTransition / useDeferredValue
    • Suspense / 懒加载
  • 一句话总结:React 优化的核心不是“缓存一切”,而是减少无效渲染、降低主线程压力、把关键更新优先做。

一、第一原则:先测量,再动刀

面试里最加分的一句话是:

性能优化不是凭感觉加 useMemo,而是先定位瓶颈。

常见定位工具包括:

  • React DevTools Profiler
  • Chrome Performance 面板
  • Lighthouse / Web Vitals
  • 打包分析工具
  • 线上慢页、卡顿、错误监控

如果不先量化,很多“优化”只是把代码复杂度变高,收益却不明显。

二、组件层:减少不必要重渲染

这是 React 优化里最核心的一层。

1. 组件边界拆分要合理

如果一个父组件掌握了过多状态,任何一点小更新都可能导致整片子树重渲染。

常见做法:

  • 把频繁变化的局部状态下沉到更小组件
  • 把大组件拆成纯展示组件和容器组件
  • 避免一个页面组件既管数据又管大量细碎 UI 状态

2. 让 props 尽量稳定

如果父组件每次 render 都创建全新的对象、数组、函数,就可能导致子组件无法跳过重渲染。

但这里要注意:

  • 不是所有新引用都值得优化
  • 只有当它真的造成明显重渲染成本时,才考虑记忆化

3. 按需使用 React.memo

适合场景:

  • 纯展示组件
  • 渲染成本明显
  • props 变化频率低

不适合场景:

  • 组件很轻
  • props 本来就经常变化
  • 为了 memo 反而写了很多难维护的比较逻辑

三、状态层:优化状态归属,而不是只优化语法

很多 React 卡顿,本质不是“组件不会 memo”,而是 状态放错层级

1. 状态就近放置

原则是:

  • 谁用,尽量谁持有
  • 不要把局部 UI 状态无脑提升到页面顶层

例如:

  • 输入框聚焦态
  • 弹窗开关
  • hover 状态

这些通常不该放到全局状态。

2. 减少 Context 的无差别广播

Context 很方便,但 Provider 的 value 一变,消费它的子树通常都会重新参与渲染。

常见优化方式:

  • 拆分多个 Context
  • 保持 Provider value 稳定
  • 高频变更状态不要粗暴塞进一个大 Context

3. 区分客户端本地状态和服务端状态

请求结果、缓存、失效、重试这类状态,最好交给专门数据层管理。

原因是它们往往需要:

  • 缓存
  • 去重
  • 失效控制
  • 后台刷新

手写 useEffect + useState 很容易越写越乱。

四、列表和高频交互层:这是最容易真的卡的地方

1. 长列表优先考虑虚拟列表

列表性能问题很多时候不是 React diff 算不动,而是:

  • DOM 太多
  • 布局和绘制太重
  • 每次更新范围过大

虚拟列表能显著减少同时存在的 DOM 数量。

2. key 必须稳定

不稳定的 key 会导致:

  • 节点复用失败
  • 状态错位
  • 额外的插入和删除

3. 高频输入和重计算分级处理

React 18 之后可以利用:

  • startTransition
  • useDeferredValue

把“用户输入立即反馈”和“重列表刷新”拆开。

一个经典场景是搜索联想:

  • 输入框回显要立刻
  • 过滤 5000 条结果可以稍后

4. 昂贵计算不要每次 render 都重复做

如果某些派生计算真的很重,例如:

  • 大数组排序
  • 复杂过滤
  • 图表数据转换

才考虑 useMemo

注意关键词是“真的很重”,不是“看到计算就 memo”。

五、资源层:别只盯着重渲染,首屏体积也很关键

1. 代码分割和懒加载

常见拆分点:

  • 路由页面
  • 大弹窗
  • 图表库
  • 富文本编辑器
  • 地图 SDK

常见方案:

  • React.lazy
  • Suspense
  • 动态 import()

2. 依赖治理

有时最大的优化不是改组件,而是去掉重依赖。

例如:

  • 减少全量工具库引入
  • 避免重复依赖进多个 chunk
  • 用更轻量替代库

3. 图片和静态资源策略

这部分虽然不专属于 React,但在 React 项目里同样重要:

  • 首屏关键图优先加载
  • 非首屏资源懒加载
  • 预加载真正关键资源
  • 控制大图和字体体积

六、渲染策略层:不是所有内容都要在客户端现算

如果项目形态允许,还可以从渲染模式上优化:

  • SSR:更快看到首屏内容
  • SSG:静态内容更早到达
  • Streaming / Suspense:更早逐步把可显示内容发给用户

这类优化往往比单纯在客户端加 memo 更高 ROI。

七、监控层:没有闭环,优化容易回退

性能优化不是一次性动作。

更成熟的实践包括:

  • 建立 Web Vitals 指标基线
  • 关键交互埋点
  • 版本对比
  • 性能预算
  • CI 中做 bundle 体积守护

否则今天优化了,明天引入一个大依赖又退回去。

八、面试里最稳的答法

标准答法

React 性能优化我一般按层来做。第一步先用 Profiler、Performance、Web Vitals 找到真实瓶颈;第二步控制重渲染范围,比如组件拆分、状态下沉、稳定 props、按需使用 React.memouseMemouseCallback;第三步针对长列表和高频交互,用虚拟列表、稳定 keystartTransitionuseDeferredValue 降低主线程压力;第四步做代码分割、懒加载和依赖治理,减少首屏包体积;最后用线上监控和性能预算验证优化是否真正生效。

九、常见追问

1. useMemouseCallback 是不是默认都该加?

不是。它们本身也有维护和比较成本,只有在确实能减少明显重复计算或避免子组件无效重渲染时才值得用。

2. 为什么说状态设计比记忆化更重要?

因为如果状态本身放得太高、共享范围太大,再多 memo 也只是补救;真正高收益的是先缩小更新影响面。

3. React 性能优化和前端通用性能优化有什么区别?

React 更关注组件更新、状态边界和调度优先级;通用前端性能还包括网络、缓存、图片、渲染管线和资源加载。

十、易错点 / 坑

  • 把性能优化答成 useMemouseCallback 清单。
  • 没有先定位就开始“优化”。
  • 把所有状态抬太高,再用大量 memo 补救。
  • 只关注 React 重渲染,不看 bundle、图片、网络和主线程长任务。
  • 首屏核心内容也盲目懒加载,结果首屏更慢。

速记要点(可背诵)

  • 先测量,再优化。
  • 先缩小更新范围,再谈记忆化。
  • 长列表靠虚拟列表,高频交互靠优先级调度。
  • 首屏优化要配合拆包、懒加载和依赖治理。