React 性能优化:事件处理器的正确使用

组件的更新

React 组件有一个 componentShouldUpdate 生命周期方法可以让我们决定组件是否需要在本次渲染时更新。合理利用会带来性能上的提升。但如果你确定你的组件

  • propsstate 确定的情况下组件有确定的输出
  • 组件的 props 不存在复杂的嵌套对象,state 更新时是整个更新而不是修改其中的某个对象

那么直接使用 PureComponent 会省事很多。它与 Component 的区别在于,其实现了 shouldComponentUpdate,对 propsstate 进行了浅比较(shallowly compare)以判断组件是否需要更新。也就是这部分工作组件内部帮我们做了。

之所以要满足上面的条件,因为它做的是线比较,如果说传入的 props 里面是复杂对象,虽然对象里面属性值变更了,但对象引用没发生变化,PureComponent 检测不到输入的变化,也就不会重新渲染了。

事件处理器的绑定

图方便的情况下将事件处理器直接肉联在 JSX 中,考察下面的示例代码:

class Button extends React.PureComponent {
  componentDidUpdate() {
    console.count("Button updated");
  }
  render() {
    return <button {...this.props}>click me</button>;
  }
}

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 1
    };
  }

  componentDidUpdate() {
    console.count("MyComponent updated");
  }
  render() {
    return (
      <>
        {this.state.count}
        <Button
          onClick={() => {
            this.setState({ count: this.state.count + 1 });
          }}
        />
      </>
    );
  }
}

点击按钮增加数字,观察控制台的输出。

Button updated: 1
MyComponent updated: 1
Button updated: 2
MyComponent updated: 2

在点击按钮时,MyComponent 的状态会更新并且重新渲染,同时,根据输出信息可以清晰地知道 Button 组件也重新渲染了。但 Button 组件内部及外部的传入其他没什么变化。

同时 Button 组件看似使用了 PureComponent 来防止不必要的更新,但似乎并没有起到作用。

原因就在于这里通过肉联方式传递的 onClick 事件处理器。按钮点击后 MyComponent 重新渲染时,会生成新的事件处理器,然后传递到 Button 组件,虽然前后两次事件处理器一模一样,但其实已经不是同一个方法了,它们在内存中的引用是不同的。这导致 Button 组件的 shouldComponentUpdate 在进行浅比较时,检测到了变更,所以 Button 重新渲染了。

因此,我们要避免使用肉联的方式书写事件处理器。

解决方法可以是将它挂载到组件这个类上面,像下面这样:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 1
    };
  }

  componentDidUpdate() {
    console.count("MyComponent updated");
  }

  clickHandler = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <>
        {this.state.count}
        <Button onClick={this.clickHandler} />
      </>
    );
  }
}

特别地,如果事件处理器中没有使用到 this,那完全可以将其从组件中移出,定义在外部。

function clickHandler() {
  console.log("hello");
}

class MyComponent extends React.Component {
  render() {
    return <Button onClick={clickHandler} />;
  }
}

这样做的好处是所有组件都使用同一个函数,节省了开销。

参考