Use of PureComponent for performance optimization of React components

Article from personal blog: https://knightyun.github.io/2021/05/09/js-react-purecomponent , reprint, please state

In the React class component, if the state changes, it will trigger the re rendering of the component (execute the render method), and it is all re rendering including all sub components, whether some sub components use the value in state or not; However, sometimes the workload of calculation or rendering of some sub components is large, and only some single display work is done, so the rendering of them when updating the status is an additional performance burden, so some optimization methods need to be sought;

shouldComponentUpdate

shouldComponentUpdate is one of the life cycle functions of React. It will be called before each rendering, and it will decide whether to call the rendering function according to the return value of the function (true/false) (return true triggers rendering, return false prevents rendering), However, the calling of shouldComponentUpdate method will not be triggered when the component is rendered for the first time or the forceUpdate() method is called; The default behavior of the lifecycle function is to trigger re rendering every time the state changes. If you declare that the function will override this default behavior, you need to judge the change of state to decide whether to re render;

shouldComponentUpdate method receives two parameters: (nextProps, nextState), which respectively represent the changed props (component parameter) and state (component state);

class MyComponent extends React.Component {
  state = { count: 0 };

  shouldComponentUpdate(nextProps, nextState) {
    // There is no need to update the state value manually, and the component will update automatically
    // this.setState({ ...nextState });

    if (nextState.count <= 3) {
      // When the count value is greater than 3, the component will not be updated
      return true;
    } else {
      return false;
    }
  }

  render() {
    const { count } = this.state;
    return (
      <button onClick={() => this.setState({ count: count + 1 })}>
        {count}
      </button>
    );
  }
}

PureComponent

React. Similar to our commonly used react. Pure component Component. The difference lies in the built-in shouldComponentUpdate logic of PureComponent. It will compare the values of props and state before and after the change at the same time. If there is no change, it will skip re rendering, which is equivalent to an additional layer of props comparison; The following is a simple example to compare the effects of the two components;

Effect comparison

Suppose there is a counter, click the button to increase the count and render the count value with two components:

class Counter extends React.Component {
  state = { count: 0 };

  render() {
    const { count } = this.state;
    return (
      <div style={{ float: "right", textAlign: 'right' }}>
        <div>count: {count}</div>
        <CountText count={count > 2 ? count : 0} />
        <ConstText count={count > 2 ? count : 0} />
        <button onClick={() => this.setState({ count: count + 1 })}>Add</button>
      </div>
    );
  }
}

// Common components
class CountText extends React.Component {
  render() {
    const { count } = this.props;
    console.log('normal rendered', count);
    return <div>normal: {count}</div>;
  }
}

// "Pure" component
class ConstText extends React.PureComponent {
  render() {
    const { count } = this.props;
    console.log('pure rendered', count);
    return <div>pure: {count}</div>;
  }
}

The rendering and output of the first rendering are as follows. When the page is initialized, both ordinary components and pure components will be rendered once:

After clicking the button for the first and second time, you can see that only ordinary components trigger rendering, even if the props value count received by the component each time does not change:

After clicking the button for the third time, due to the change of the incoming value of props, the pure component also triggers re rendering, and the page content is updated normally:

Sub component update problem

It can be seen that PureComponent can indeed avoid re rendering when the incoming props value does not change, and optimize the performance in some scenes. However, this is also a premise for using PureComponent, that is, components with the same props incoming value will always have the same rendering content, that is, the meaning of Pure in Pure components, It is somewhat similar to the definition of a Pure function (after passing in the same parameters and executing, you will always get the same return value);

On the other hand, when PureComponent skips rendering, all its subcomponents will also skip rendering, even if the subcomponents should be updated, so it is necessary to ensure that all subcomponents of pure components are also pure components; For example, the following pure component contains a sub component showing the current time:

class Counter extends React.Component {
  state = { count: 0 };

  render() {
    const { count } = this.state;
    return (
      <div style={{ float: "right", textAlign: 'right' }}>
        <div>count: {count}</div>
        <ConstText count={count > 2 ? count : 0} />
        <button onClick={() => this.setState({ count: count + 1 })}>Add</button>
      </div>
    );
  }
}

// "Pure" component
class ConstText extends React.PureComponent {
  render() {
    const { count } = this.props;
    const d = new Date();
    const time = `${d.getHours()}: ${d.getMinutes()}: ${d.getSeconds()}`;
    console.log('pure rendered', count);

    return (
      <div>
        pure: {count}
        <ConstChild time={time} />
      </div>
    );
  }
}

// Show time sub components
class ConstChild extends React.Component {
  render() {
    const { time } = this.props;
    console.log('child rendered', time);
    return <div>{time}</div>
  }
}

During page initialization:

After the first two clicks:

At this time, neither the pure component nor its sub components trigger the update. The update is triggered at the same time after the third Click:

Shallow contrast problem

At the beginning, it was mentioned that PureComponent is to make a shallow comparison between the values before and after the change of props to determine whether to re render the component. In fact, it is to make a basic value comparison for each props value. If the value type is a complex type, such as reference type (object), it will not deeply traverse the change of each attribute. Let's transform the above example, Turn the props passed into a pure component into a reference object:

class Counter extends React.Component {
  state = { count: 0 };
  obj = { num: [0] };

  handleAdd() {
    const newCount = this.state.count + 1;
    this.setState({ count: newCount });
    this.obj.num[0] = newCount;
  }

  render() {
    const { count } = this.state;
    console.log('Counter rendered', count, JSON.stringify(this.obj));

    return (
      <div style={{ float: "right", textAlign: 'right' }}>
        <div>count: {count}</div>
        <ConstText count={this.obj} />
        <button onClick={() => this.handleAdd()}>Add</button>
      </div>
    );
  }
}

class ConstText extends React.PureComponent {
  render() {
    const { count: { num: [count] } } = this.props;
    console.log('pure rendered', count);
    return <div>pure: {count}</div>;
  }
}

Results after initial initialization:

Results after clicking three times in sequence:

It can be seen that the actual value of props passed into a pure component changes every time, but because it is a reference type, the component does not recognize this change and skips the component update forever; Therefore, if you encounter complex data structures, try to use state, because the value of state after each change is a new value due to its own characteristics and rules;

Of course, there is another special case. If Render prop or similar form is used in the component, that is, the value of props is a function that returns a certain value, such as:

<Counter count={() => 3} />

Then, even if the value actually executed by the function is the same every time, it will trigger rendering, because the function itself will be judged as a new value every time, making the performance optimization invalid;

React.memo

React.memo is a high-level component similar to PureComponent, except that it is used for function components and PureComponent is used for class components. However, the actual display of the two is consistent with the optimization effect. The following are two component forms:

// Class component
class ConstText1 extends React.PureComponent {
  render() {
    const { count } = this.props;
    console.log('pure rendered', count);
    return <div>pure: {count}</div>;
  }
}

// Function component
const ConstText2 = React.memo(function(props) {
  const { count } = props;
  console.log('const rendered', count);
  return <div>{count}</div>
});

Keywords: Javascript React Optimize

Added by smartknightinc on Thu, 17 Feb 2022 15:17:09 +0200