返回

避免子组件re-rend:React性能优化的秘籍

前端

React的性能优化的一个方面就是避免组件的重复渲染(re-rend)。官方文档也提供了很过性能优化的奇技淫巧,且大部分都有一个比较明确的上下文。这里我们讨论两种常见的情况。

应该更新吗?

从字面意思上看,shouldComponentUpdate 这个生命周期函数明确告诉我们,他是用于优化,防止不必要的组件更新的 。那么在合适的情况,我们怎么正确使用它呢?

PureComponent 是 shouldComponentUpdate 的包装类,默认实现的就是shallow check。它会做以下3件事:

  • 它不会自动绑定 this,但你可以手动绑定它
  • 它会做shallow check 来判断组件的状态或者属性是否有变化
  • 如果你想做更复杂的比较,你仍然可以重写它的 shouldComponentUpdate 方法

为什么是 shallow check 呢?

因为它会只检查你手动定义的 props 和 state 变化,如果你使用了 Redux,并使用 connect 对组件进行装饰,那么 由于连接组件的 props 是一层一层包起来的,所以进行比较的时候还是会执行重新渲染

因此,如果我们想使用 PureComponent 做深层比较的话,我们可以在 state 和 props 变化时,通过手动制造一个新的对象,将它们都 assign 到一个新对象上。这样,即使数据源是同一个,新对象和旧对象相比较,都是两个不同的对象了。

因此,在默认情况下,使用 PureComponent 相比于原生使用 shouldComponentUpdate 带来以下好处:

  • 避免了手动绑定 this
  • 强迫我们对所有的 props 和 state 手动实现比较

不要滥用 shouldComponentUpdate 和 PureComponent,因为它们不是万能的,存在如下不足:

  • 它不能做层级深层比较
  • 在类的构造函数中,调用 this.setState() 不会触发 shouldComponentUpdate

另外,当我们的需求明确的时候,我们可以手动编写 shouldComponentUpdate 的判断逻辑,实现比 PureComponent 更灵活的判断方法。

备忘录模式

备忘录模式和 shouldComponentUpdate/PureComponent 类似,它们都是为了减少组件重新渲染的次数。备忘录模式可以用来记住上次渲染时使用的 props 和 state,然后在组件更新时,比较新的 props 和 state 与上次渲染时保存的 props 和 state。如果它们没有发生变化,则组件不会重新渲染。

备忘录模式的实现方法有很多种,一种常见的方法是使用一个类来保存上次渲染时使用的 props 和 state。在组件更新时,比较新的 props 和 state 与上次渲染时保存的 props 和 state。如果它们没有发生变化,则组件不会重新渲染。

class MemoizedComponent extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      memoizedProps: null,
      memoizedState: null,
    };
  }

  shouldComponentUpdate(nextProps, nextState) {
    return (
      this.state.memoizedProps !== nextProps ||
      this.state.memoizedState !== nextState
    );
  }

  componentDidUpdate(prevProps, prevState) {
    this.setState({
      memoizedProps: prevProps,
      memoizedState: prevState,
    });
  }

  render() {
    return <WrappedComponent {...this.props} />;
  }
}

备忘录模式的优点是,它可以很容易地应用于任何组件。缺点是,它可能会增加组件的复杂性,并且可能导致内存泄漏。

总之,shouldComponentUpdate/PureComponent 和备忘录模式都是非常有用的工具,可以用来减少组件重新渲染的次数。然而,在使用它们之前,应该仔细考虑组件的具体情况,以避免引入不必要的复杂性和内存泄漏。