Skip to content

React 性能优化 ​

「性能优化」的核心应当是「不订阅视图无关的数据,以避免重复渲染」

而不是「订阅了视图无关的数据,用 useCallback/useMemo 来缓解性能问题」

——苏晗若

说 React 性能优化,其实是想让页面更快加载出来,更流畅。他的本质是减少渲染真实 DOM 节点的频率,减少虚拟 DOM 对比的频率

在 Fiber 一文中,我们讲过 React Fiber 的工作流,简单来说,它分为两个阶段:render 和 commit

Reconciler(协调器)工作的阶段被称为 render 阶段,Renderer(渲染器)工作的阶段被称为 commit 阶段

render 阶段会进行多次的调用(会被高优先级的任务打断,重新调用)

commit 阶段会执行一次调用(虚拟DOM 渲染到真实DOM上)

所以我们就要对着两个阶段进行优化, render 阶段的优化就是减少不变要的更新

触发更新的方式有:forceUpdate、State更新、父组件 Render 触发子组件 Render

commit 阶段是将虚拟DOM渲染到真实DOM上,所以我们需要在这阶段更新 state

简而言之,我们可以把优化方案分为三类

  1. render 阶段优化。即跳过不必要的组件的渲染更新
  2. commit 阶段优化。即减少 commit 阶段耗时
  3. 非渲染方面的优化

render 阶段优化,即跳过不必要的组件更新 ​

PureComponent、React.memo ​

如果只有父组件发生状态更新,即使父组件传给子组件的所有 Props 都没有修改,也会引起子组件的 Render 过程。PureComponent 和 React.memo 就是应对这种场景的

PureComponent 是对类组件的 Props 和 State 进行浅比较

React.memo 是对函数组件的 Props 进行浅比较

浅比较:只会比较 props 对象的第一层属性,不会深入比较嵌套对象或数组的内部结构

useMemo、useCallback 实现稳定的 Props 值 ​

如果传给子组件的派生状态或函数,每次都是新的引用,那么 PureComponent 和 React.memo 优化就会失效。所以需要使用 useMemo 和 useCallback 来生成稳定值,并结合 PureComponent 或 React.memo 避免子组件重新 Render

useMemo 缓存(复杂)值

useCallback 缓存函数

列表项使用 key 属性 ​

为了 diff 算法能对比,需要给列表项中的每个值加上 key 属性

shouldComponentUpdate ​

只适用于 class 组件,已经淘汰

commit 阶段优化 ​

这类优化的目的是减少提交阶段耗时,该分类中仅有一条优化技巧

避免在 didMount、didUpdate 中更新组件 State

React 提交阶段的第二步是执行 commit 阶段钩子,它的执行会阻塞浏览器更新页面。如果在提交阶段钩子函数更新组件 State,会再次触发组件的更新流程,造成两倍耗时

非渲染方面的优化 ​

组件按需挂载 ​

可分为懒加载、懒渲染和虚拟列表三类

懒加载 ​

懒加载的实现是通过 Webpack 的动态导入和 React.lazy 方法

懒渲染 ​

当组件进入或即将进入可视区域时才渲染组件。常见的组件 Modal/Drawer 等,当 visible 属性为 true 时才渲染组件内容,也可以认为是懒渲染的一种实现

虚拟列表 ​

虚拟列表是懒渲染的一种特殊场景,不渲染所有的数据,只渲染可视区域中的数据。当用户滑动时,通过监听 scroll 来判断是上滑还是下拉,从而更新数据。同理 IntersectionObserver(交叉观察者) 和 getBoundingClientRect 都能实现

  • IntersectionObserver:监听一个目标与它祖先元素或顶级文档视窗的交叉状态

  • getBoundingClientRect :返回元素的大小及其相对视图的位置

参考资料 ​