react re-render 优化

2,601 阅读3分钟

React 作为一个视图框架,一般情况下速度已经很快了,并且在 React16 新推出的 Fiber 架构中,通过时间切片及高优先级任务中断来尽快相应用户的操作。但是有些我们忽略点,造成了不必要的组件 re-render,从而造成性能的浪费。

如何阻止不必要的 re-render

使用 pureComponent

当组件更新时,pureComponent 会在 shouldComponentUpdate 帮我们比较一下 propsstate, 如果组件的 propsstate 都没发生改变,render 方法就不会触发。具体就是 React 自动帮我们做了一层浅比较:

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

shallowEqual 又做了什么呢?会去判断 prevnext 的 key 是否一致,且对应的值跟引用是否相等。(注意这里是引用是否相等,两个即使内容相同的对象,也是不相等的)

虽然有了一层浅比较,但是更多的时候,由于我们的写法的问题,使得这层比较失效了。

// Child extends PureComponent
class Parent extends Component {
  render() {
    return (
      <div>
        <Child
          style={{ width: 100 }}
          onClick={() => {
            // do something
          }}
        />
      </div>
    );
  }
}

上面的这种写法,会导致每次 Child 必定重复更新,因为每次传递进来的 onClick 和 style 都是与上次不相等的。

将这些不变的数据使用同一个引用

// Child extends PureComponent
class Parent extends Component {
  onChildClick = () => {
    // do something
  };
  childStyle = { width: 100 };
  render() {
    return (
      <div>
        <Child onClick={this.onChildClick} style={this.childStyle} />
      </div>
    );
  }
}

使用 React.memo

React.memo 为高阶组件。它与 React.PureComponent 非常相似,但它适用于函数组件,但不适用于 class 组件。

如果你的函数组件在给定相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

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

// 如果 areEqual 不传递的话使用默认的浅比较
export default React.memo(MyComponent, areEqual);

再说说上面同一个引用的问题,假设现在有下面这样一个组件

const Child = React.memo(MyComponent);
const Parent = () => {
  onChildClick = {
    // do something
  };

  return (
    <div>
      <Child onClick={onChildClick} />
    </div>
  );
};

这里就会有上面说的问题,每一次re-render 的时候 传递给 Child 的 onClick 都是新生产的 function,所以导致浅比较失效。 这里我们可以用 useCallback 去进行优化。(关于 useCallback 简单使用介绍请查看 React hooks useCallback 指北)

const Child = React.memo(MyComponent);
const Parent = () => {
  const onChildClick = useCallback(() => {
   // do something
  }, []);

  return (
    <div>
      <Child onClick={onChildClick} />
    </div>
  );
};

使用 react-redux 时候,优化 re-render

可以在传递 props 时就做对比优化,不用再 shouldComponentUpdate 阶段去做

  • 使用了 connect,每次store更新,不管是否与自身有关,都会导致组件re-render。其实 react-redux 是支持我们去做优化的。
/*
options
{
  context?: Object,
  pure?: boolean,
  areStatesEqual?: Function,
  areOwnPropsEqual?: Function,
  areStatePropsEqual?: Function,
  areMergedPropsEqual?: Function,
  forwardRef?: boolean,
}
*/
function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)

我们可以通过 areStatePropsEqual 函数去做对比优化。

  • 如果使用了 hooks,怎么优化呢
const result : any = useSelector(selector : Function, equalityFn? : Function)

通过 equalityFn 去做优化。

总结

  1. 类组件使用 PureComponent 去做优化,函数组件使用 React.memo 去做优化
  2. 将这些不变的数据使用同一个引用,减少匿名函数的使用
  3. react-redux 提前关注我们store,对相关store做处理

参考

React.memo 和 PureComponent 文档 不要滥用 React.memo