React 项目性能优化怎么讲

一句话先定性

我不会把 React 性能优化讲成“到处上 memouseMemo”,而会讲成“围绕渲染链路、主线程压力和数据流边界去做定位和治理”。


30 秒版本

如果面试官问 React 性能优化,我一般会先分三类。第一类是首屏慢,第二类是交互卡,第三类是长列表和复杂组件树渲染过重。对应动作也很清楚:首屏慢看拆包、懒加载和关键资源;交互卡看主线程长任务和重复渲染;列表卡看虚拟列表、状态拆分和渲染边界。这样讲会比单纯背优化手段更像项目经验。

1 分钟版本

我做 React 性能优化时,一般不会先上来就加缓存,而是先判断问题在哪一层。比如首屏慢,更多是资源体积和渲染链路问题;输入卡顿,更多是主线程被同步计算和重复渲染占住了;列表滚动卡,通常是渲染量太大。

对应手段上,首屏我会做代码拆包、路由懒加载、图片和字体优化;交互我会减少不必要渲染、拆长任务、把非关键逻辑延后;列表和复杂表格我会做虚拟列表、局部更新和组件边界拆分。React 层面常见动作就是 memouseMemouseCallback、状态下沉、状态就近放置,但我会强调这些只是手段,不是目的。

真正重要的是先定位再优化,比如用 React Profiler、浏览器 Performance 面板和 Web Vitals 看问题到底出在哪,不然很容易做成“优化动作很多,但效果不明显”。

2 到 3 分钟版本

React 项目性能优化这个题,我一般会先讲一套排查框架,而不是直接开始背 API。因为 React 性能问题本质上还是前端性能问题,只是它更容易体现在组件树、状态更新和渲染边界上。

我通常会先分三层看。第一层是加载性能,也就是包体积、路由切换、首屏资源和初始化执行量。第二层是渲染性能,也就是某次状态更新为什么会带出过大的组件树重渲染。第三层是交互性能,也就是输入、点击、滚动这些用户动作之后,主线程有没有被长任务堵住。

如果是首屏慢,我会先看包是不是过大、是不是把很多非首屏逻辑同步打进来了、是不是图片和字体阻塞了渲染。这个时候常用手段就是路由级代码拆包、组件懒加载、资源压缩、减少首屏同步计算。如果是 React SSR 或混合渲染场景,我还会关注 hydration 阶段是不是太重。

如果是渲染性能问题,我最先看的不是 useMemo,而是状态边界是不是放大了。比如把一个页面级大状态放在最上层,导致任何小改动都让整棵树重渲染,这种问题比单个组件慢更常见。所以我通常会先做状态就近放置、拆组件边界、减少无意义 props 透传,再考虑 memouseMemouseCallback 这些局部优化。我的原则是先缩小更新影响范围,再谈缓存。

如果是交互卡,比如输入框卡顿、拖拽卡顿、筛选条件一改整个页面顿一下,我会优先怀疑主线程是不是有长任务。比如一次输入触发了很重的过滤、排序、格式化,或者一个 Effect 里做了太多同步工作。这时候我会考虑把计算拆分、延后非关键逻辑、必要时上 useTransitionuseDeferredValue,或者把特别重的计算扔到 Web Worker。

另外长列表和大表格是 React 项目里非常典型的场景。这个时候我一般不会试图优化单个 cell,而是优先把渲染量降下来,比如虚拟列表、分段渲染、行级 memo 和局部刷新。因为这类问题很多时候不是“每个组件太慢”,而是“同时渲染的组件太多”。

如果让我最后总结,我会说 React 性能优化不是某个 hook 的技巧题,而是一个“先定位瓶颈,再控制渲染范围,再降低主线程压力”的过程。真正有经验的回答,不是会背多少 API,而是知道应该先查包体积、状态边界、组件树还是长任务。

如果面试官追问“为什么不建议一上来就用 useMemo”

因为 useMemouseCallback 本身也有成本,而且很多时候真正的问题不是某个计算慢,而是状态边界太大,导致整棵树重复渲染。先把更新范围收小,通常比到处加 memo 更有效。

如果面试官追问“怎么判断是不是重复渲染问题”

我一般会先用 React Profiler 看某次交互之后到底是谁在重复 render,再结合浏览器 Performance 看主线程时间花在哪。也就是说,先证明确实是渲染问题,再决定是拆状态、拆组件,还是做 memo。

如果面试官追问“长列表怎么优化”

我会优先做虚拟列表,因为它是直接减少渲染数量。然后再看行级 memo、局部更新、滚动时避免做重计算。长列表问题很多时候不是算法慢,而是一次渲染太多 DOM。

最后一句收尾

所以如果面试里问 React 性能优化,我会按“首屏、交互、渲染边界”三条线去讲,再把每个问题对应到具体动作上,这样会比单纯背 memouseMemo 更像真实项目经验。

相关追问