react性能优化

832 阅读3分钟

PureComponent

React.PureComponentReact.Component 很相似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate(),而 React.PureComponent 中以浅层对比 prop 和 state 的方式来实现了该函数。

if (this._compositeType === CompositeTypes.PureClass) {
  shouldUpdate = !shallowEqual(prevProps, nextProps) || ! shallowEqual(inst.state, nextState);
}

PureComponent不仅会影响本身,而且会影响子组件,所以PureComponent最佳情况是展示组件。

shallowEqual 会比较 Object.keys(state | props) 的长度是否一致,每一个 key 是否两者都有,并且是否是 一个引用,也就是只比较了 第一层 的值,确实很浅,所以深层的嵌套数据是对比不出来的。

此外,React.PureComponent 中的 shouldComponentUpdate() 将跳过所有子组件树的 prop 更新。因此,请确保所有子组件也都是“纯”的组件。

所以当我们在传递引用类型数据的时候,shouldComponentUpdate()React.PureComponent存在一定的局限性。

针对这个问题,官方给出的两个解决方案:

  • 在深层数据结构发生变化时调用forceUpdate()来确保组件被正确地更新(不推荐使用);
    • forceUpdate()方法强制更新子组件,forceUpdate()会跳过子组件的shouldComponentUpdate()
  • 使用immutable对象加速嵌套数据的比较(不同于深拷贝);
    • 持久性数据结构的库,持久性指的是数据一旦创建,就不能再被更改,任何修改或添加删除操作都会返回一个新的 Immutable 对象,提供了简洁高效的判断数据是否变化的方法,只需 === 和 is 比较就能知道是否需要执行 render(),而这个操作几乎 0 成本,所以可以极大提高性能。

【注】通过在PureComponent上实现自己的shouldComponentUpdate,将失去浅层比较。

首先在开发环境中获取警告,因为React源代码检查以查看在处理PureComponent时是否定义了方法。

if (
  isPureComponent(Component) &&
  typeof inst.shouldComponentUpdate !== 'undefined'
) {
  warning(
    false,
    '%s has a method called shouldComponentUpdate(). ' +
      'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
      'Please extend React.Component if shouldComponentUpdate is used.',
    this.getName() || 'A pure component',
  );
}

然后,在渲染时,如果定义了这个方法,它实际上会跳过,甚至不检查该组件是否是PureComponent并使用您自己的实现。

if (inst.shouldComponentUpdate) {
  if (__DEV__) {
    shouldUpdate = measureLifeCyclePerf(
      () => inst.shouldComponentUpdate(nextProps, nextState, nextContext),
      this._debugID,
      'shouldComponentUpdate',
    );
  } else {
    shouldUpdate = inst.shouldComponentUpdate(
      nextProps,
      nextState,
      nextContext,
    );
  }
} else {
  if (this._compositeType === ReactCompositeComponentTypes.PureClass) {
    shouldUpdate =
      !shallowEqual(prevProps, nextProps) ||
      !shallowEqual(inst.state, nextState);
  }
}

shouldComponentUpdate

当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true。目前,如果shouldComponentUpdate返回 false,则不会调用UNSAFE_componentWillUpdate()render()componentDidUpdate()方法。后续版本,React 可能会将shouldComponentUpdate()视为提示而不是严格的指令,并且,当返回 false 时,仍可能导致组件重新渲染。

shouldComponentUpdate方法接收两个参数nextPropsnextState,可以将this.propsnextProps以及this.statenextState进行比较,并返回 false 以告知 React 可以跳过更新。

shouldComponentUpdate (nextProps, nextState) {
  return true
}

React.Memo

React.PureComponent 非常相似,只在函数组件使用,是一个高阶组件。

只比较props,如果函数组件被 React.memo 包裹,且其实现中拥有 useState 或 useContext 的 Hook,当 context 发生变化时,它仍会重新渲染。

默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

function MyComponent(props) {
  /* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
  /*
  如果把 nextProps 传入 render 方法的返回结果与
  将 prevProps 传入 render 方法的返回结果一致则返回 true,
  否则返回 false
  */
}
export default React.memo(MyComponent, areEqual)

总结

  1. 类组件中:shouldComponentUpdate()React.PureComponent 在基本类型数据传递时都可以起到优化作用,当包含引用类型数据传递的时候,shouldComponentUpdate()更合适一些,另外pure子组件。
  2. 函数组件:使用 React.memo