React组件性能优化
React 性能优化思路
我觉得React 性能优化的理念的主要方向就是这两个:
- 减少重新 render 的次数。因为在 React 里最重(花时间最长)的一块就是 reconciliation(简单的可以理解为 diff),如果不 render,就不会 reconciliation。
- 减少计算的量。主要是减少重复计算,对于函数式组件来说,每次 render 都会重新从头开始执行函数调用。
在使用类组件的时候,使用的 React 优化 API 主要是:shouldComponentUpdate
和 PureComponent
,这两个 API 所提供的解决思路都是为了减少重新 render 的次数,主要是减少父组件更新而子组件也更新的情况。
但是在函数式组件里面没有声明周期也没有类,那如何来做性能优化呢?
先分个类,组件什么时候会重新执行?
- 组件自己的状态改变
- 父组件重新渲染,导致子组件重新渲染,但是父组件的 props 没有改变
- 父组件重新渲染,导致子组件重新渲染,但是父组件传递的 props 改变
针对第二点,在FC中,可以通过memo减少rerender
1 | function Component(props) { |
通过 React.memo
包裹的组件在 props 不变的情况下,这个被包裹的组件是不会重新渲染的(相当于PureComonent)
默认情况下其只会对 props 的复杂对象做浅层对比(浅层对比就是只会对比前后两次 props 对象引用是否相同,不会对比对象里面的内容是否相同),如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。
1 | function MyComponent(props) { |
组件性能优化
React 组件是一个树形结构,且每个节点都是懒计算的(类似于 Thunk 的概念)。当一个节点不需要重新计算(重绘)时,他的子树都不会计算(重绘)。所以我们做性能优化的目标,就是在尽量离根节点近的位置,拦截不必要的节点重算,从而减少重绘的计算量。
React.memo
阻止节点重绘主要通过 React.memo 方法生成特殊的组件节点。它接受两个传参:
1 | React.memo(Component, areEqual); |
Component:组件
areEqual:比较函数
比较函数的入参有两个,arg0 为 前一次渲染的 props, arg1 为本次渲染的 props。如果返回 true,则该节点本次渲染将被标记为无需重新计算,从而使其所有子节点、孙子节点都无需计算。
areEqual 如果不传,默认使用
1 | (prevProps, nextProps) => shallowEqual(prevProps, nextProps) |
做为比较函数。
如何定位优化点
使用前一章节提到 React DevTools 中的 Profiling 功能,record 发生卡顿的操作,从耗时长的组件逐个查看,找到那些跟此次操作无关的上层渲染节点,尝试使用 React.memo 包裹这些组件。
不要为了优化而优化
在没有性能问题前,不用去纠结是否要用 Profiling、React.memo、useMemo、useCallback 去优化性能,这些不一定能带来性能提升,反而肯定会带来首屏的性能下降。大多数情况下,React 现有算法以能满足性能需求。
对于一个组件,有三样东西会让她重绘
- State 变更
- 依赖的 context 变更
- 父组件重绘
所以用 React.memo 包裹之后,并不是说性能就会有多大的提高。如果组件中依赖的 context 中,有一部分并不是此组件需要的数据,但会经常变更,也会导致组件经常重绘。这时候我们可以增加一层组件,把依赖 context 中的数据,通过增加的一层父组件取出来,然后通过 props 传给真正渲染的组件,把 React.memo 加在真正渲染的组件上,来达到屏蔽 context 变更引起的重绘问题。
1 | import { useMemo, useContext } from 'react'; |
更通用点,可以封装出 react-redux 的 connect 函数,传入 selector 来取所需的 Context 数据。