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 进行浅比较

浅比较:对原始类型,钱比较直接判断它们的值是否相等;对于引用数据类型,浅比较会比较它们的引用地址是否相同

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 :返回元素的大小及其相对视图的位置

参考资料