shouldComponentUpdate, PureComponent and React memo

Posted by murtuza.hasan.13 on Tue, 25 Jan 2022 03:06:08 +0100

shouldComponentUpdate, PureComponent and React memo

preface

Recently, I have been learning about React and was lucky to get an opportunity to use it in practical projects. So I intend to record the problems I encounter in the process of learning and development (React) in the form of blog.

In the past two days, I have encountered the problem of unnecessary repeated rendering of components. After reading the official documents and the introduction of everyone on the Internet for many times, I will summarize it through some demo s combined with my own understanding, and take it as the first note of learning React (learning by myself is good, just wasting my hair...).

This paper mainly introduces the following three optimization methods (the three methods have similar implementation principles):

  • shouldComponentUpdate
  • React.PureComponent
  • React.memo

shouldComponentUpdate and react Purecomponent is an optimization method in class components, while react Memo is an optimization method in function components.

elicit questions

  1. Create a new Parent class component.
import React, { Component } from 'react'
import Child from './Child'

class Parent extends Component {
  constructor(props) {
    super(props)
    this.state = {
      parentInfo: 'parent',
      sonInfo: 'son'
    }
    this.changeParentInfo = this.changeParentInfo.bind(this)
  }

  changeParentInfo() {
    this.setState({
      parentInfo: `Parent component changed state: ${Date.now()}`
    })
  }

  render() {
    console.log('Parent Component render')
    return (
      <div>
        <p>{this.state.parentInfo}</p>
        <button onClick={this.changeParentInfo}>Change parent component state</button>
        <br/>
        <Child son={this.state.sonInfo}></Child>
      </div>
    )
  }
}

export default Parent


  1. Create a new Child class component.
import React, {Component} from 'react'

class Child extends Component {
  constructor(props) {
    super(props)
    this.state = {}
  }

  render() {
    console.log('Child Component render')
    return (
      <div>
        Here is child Subcomponents:
        <p>{this.props.son}</p>
      </div>
    )
  }
}

export default Child


  1. Open the console and we can see that Parent Component render and Child Component render are output successively in the console.

Click the button and we will find that Parent Component render and Child Component render are output again. When we click the button, we only change the value of parentInfo in the Parent component Parentstate. When the Parent is updated, the Child component is also re rendered, which is certainly something we don't want to see. So let's introduce the main content of this paper around this problem.

shouldComponentUpdate

React provides the life cycle function shouldComponentUpdate(). According to its return value (true | false), it can judge whether the output of react component is affected by the change of current state or props. The default behavior is that each time the state changes, the component will be re rendered (which explains the above) 👆 Reason for Child component re rendering).

Quote a paragraph from Official website Description of:

When props or state changes, shouldComponentUpdate() is called before rendering execution. The return value defaults to true. Currently, if shouldComponentUpdate returns false, unsafe will not be called_ Componentwillupdate(), render() and componentDidUpdate() methods. In subsequent versions, React may treat shouldComponentUpdate() as a prompt rather than a strict instruction, and may still cause the component to re render when false is returned.

The shouldComponentUpdate method receives two parameters, nextProps and nextState. You can set this Props and nextProps and this State is compared with nextState and returns false to tell React that the update can be skipped.

shouldComponentUpdate (nextProps, nextState) {
  return true
}

Now that we know the function of shouldComponentUpdate function, let's add the following code to the Child component:

shouldComponentUpdate(nextProps, nextState) {
    return this.props.son !== nextProps.son
}

At this time, when you click the button to modify the value of parentInfo in the parent component state, the Child component will not be re rendered.

A note here is that we pass basic type data from Parent component to Child component. If we pass reference type data, we need to make a deep comparison in shouldComponentUpdate function. However, this method has a great impact on efficiency and will damage performance. Therefore, we can consider using this method (i.e. this. Props. Son! = = nextprops. Son) for performance optimization because the data we are passing is the basic type.

(for the introduction of basic type data and reference type data, please refer to this article: Portal)

React.PureComponent

React.PureComponent and react Component is very similar. The difference between the two is react Component does not implement shouldComponentUpdate, but react Purecomponent implements this function by shallow comparison between prop and state.

Is it convenient to modify the content of the Child component to the following content.

import React, { PureComponent } from 'react'

class Child extends PureComponent {
  constructor(props) {
    super(props)
    this.state = {
    }
  }

  render() {
    console.log('Child Component render')
    return (
      <div>
        Here is child Subcomponents:
        <p>{this.props.son}</p>
      </div>
    )
  }
}

export default Child


Therefore, when the props and state of the component are both basic types, use react Purecomponent can optimize performance.

If the object contains complex data structures, there may be wrong comparison results because the deep differences cannot be checked.

In order to better understand the problem of reference type data transmission, let's rewrite the above example:

  • Modify the Child component.
import React, {Component} from 'react'

class Child extends Component {
  constructor(props) {
    super(props)
    this.state = {}
  }

  shouldComponentUpdate(nextProps, nextState) {
    return this.props.parentInfo !== nextProps.parentInfo
  }

  updateChild () {
    this.forceUpdate()
  }

  render() {
    console.log('Child Component render')
    return (
      <div>
        Here is child Subcomponents:
        <p>{this.props.parentInfo[0].name}</p>
      </div>
    )
  }
}

export default Child


  • Modify the Parent component.
import React, { Component } from 'react'
import Child from './Child'

class Parent extends Component {
  constructor(props) {
    super(props)
    this.state = {
      parentInfo: [
        { name: 'Ha ha ha' }
      ]
    }
    this.changeParentInfo = this.changeParentInfo.bind(this)
  }

  changeParentInfo() {
    let temp = this.state.parentInfo
    temp[0].name = 'Interesting:' + new Date().getTime()
    this.setState({
      parentInfo: temp
    })
  }

  render() {
    console.log('Parent Component render')
    return (
      <div>
        <p>{this.state.parentInfo[0].name}</p>
        <button onClick={this.changeParentInfo}>Change parent component state</button>
        <br/>
        <Child parentInfo={this.state.parentInfo}></Child>
      </div>
    )
  }
}

export default Parent


At this time, you can see on the console that both Parent and Child render once, and the displayed contents are consistent.

Click the button, then the problem comes. As shown in the figure, the Parent component is re rendered. From the page, we can see that the parentInfo in the Parent component has indeed changed, but the child component has not changed.

So when we pass reference type data, shouldComponentUpdate() and react Purecomponent has some limitations.

There are two official solutions to this problem:

  • When the deep data structure changes, call forceUpdate() to ensure that the component is updated correctly (not recommended);
  • Use immutable object to accelerate the comparison of nested data (different from deep copy);

forceUpdate

When we know that the Parent component modifies the data of the reference type (the rendering of the child component depends on this data), we call the forceUpdate() method to force the update of the child component. Note that forceUpdate() will skip the shouldComponentUpdate() of the child component.

Modify the Parent component (expose the subcomponents through ref to the parent component, and call the sub components after clicking the button to force the update sub components. At this point, we can see that the subcomponents are rerendered after the parent component is updated).

{
  ...
  changeParentInfo() {
    let temp = this.state.parentInfo
    temp[0].name = 'Interesting:' + new Date().getTime()
    this.setState({
      parentInfo: temp
    })
    this.childRef.updateChild()
  }
  
  render() {
    console.log('Parent Component render')
    return (
      <div>
        <p>{this.state.parentInfo[0].name}</p>
        <button onClick={this.changeParentInfo}>Change parent component state</button>
        <br/>
        <Child ref={(child)=>{this.childRef = child}} parentInfo={this.state.parentInfo}></Child>
      </div>
    )
  }
}


immutable

Immutable.js is a library of persistent data structures released by Facebook in 2014. Persistence means that once the data is created, it can no longer be changed. Any modification, addition or deletion will return a new immutable object. It can make it easier for us to deal with cache, fallback, data change detection and other problems, and simplify development. It also provides a large number of methods similar to native JS, as well as the features of Lazy Operation and complete functional programming.

Immutable provides a concise and efficient method to judge whether the data changes. You can know whether to execute render() by comparing = = = with is. This operation costs almost zero, so it can greatly improve the performance. First, code this. that calls the subcomponents in the Parent component is updated. childRef. Updatechild(), and then modify the shouldComponentUpdate() method of the Child component:

import { is } from 'immutable'

shouldComponentUpdate (nextProps = {}, nextState = {}) => {
  return !(this.props === nextProps || is(this.props, nextProps)) ||
      !(this.state === nextState || is(this.state, nextState))
}

At this time, we can find that the sub components are re rendered by looking at the results of the console and page.

For the optimization of shouldComponentUpdate() function, see above 👆 The method has yet to be verified. It is only used as a demo. In the actual development process, it may be necessary to further explore what kind of plug-in to choose and what kind of judgment method is the most comprehensive and appropriate. If you have good suggestions and relevant articles, welcome to smash them~

React.memo

About react Introduction to memo, Official website The description is very clear. I'll copy it directly here~

React.memo is a high-order component. It works with react Purecomponent is very similar, but only applies to function components, not class components.

If your function component renders the same result given the same props, you can wrap it in React In memo, the performance of the component is improved by way of memory component rendering results. This means that in this case, React will skip the operation of the rendering component and directly reuse the results of the last rendering.

React.memo only checks for props changes. If the function component is react Memo package, and its implementation has a Hook with useState or useContext. When the context changes, it will still be re rendered.

By default, it only makes shallow comparison for complex objects. If you want to control the comparison process, please pass in the custom comparison function through the second parameter.

function MyComponent(props) {
  /* Rendering with props */
}
function areEqual(prevProps, nextProps) {
  /*
  If nextProps is passed into the render method, the returned result is the same as
  If the return results of prevProps passed into render method are consistent, true will be returned,
  Otherwise, false is returned
  */
}
export default React.memo(MyComponent, areEqual)

Rewrite the above example using function components:

Child components:

import React, {useEffect} from 'react'
// import { is } from 'immutable'

function Child(props) {

  useEffect(() => {
    console.log('Child Component')
  })

  return (
    <div>
      Here is child Subcomponents:
      <p>{props.parentInfo[0].name}</p>
    </div>
  )
}

export default Child


Parent component:

import React, {useEffect, useState} from 'react'
import Child from './Child'

function Parent() {

  useEffect(() => {
    console.log('Parent Component')
  })

  const [parentInfo, setParentInfo] = useState([{name: 'Ha ha ha'}])
  const [count, setCount] = useState(0)

  const changeCount = () => {
    let temp_count = count + 1
    setCount(temp_count)
  }
  return (
    <div>
      <p>{count}</p>
      <button onClick={changeCount}>Change parent component state</button>
      <br/>
      <Child parentInfo={parentInfo}></Child>
    </div>
  )
}

export default Parent


After running the program, perform the same operation as the above example. We will find that with the modification of the count value of the parent component, the child component is also rendering repeatedly. Because it is a function component, we can only use react Memo high-order components to skip unnecessary rendering.

Modify the export method of child components: export default react memo(Child).

Running the program again, we can see that although the parent component modified the count value, the child component skipped rendering.

Here I use the writing method of React hooks. When useState in hooks modifies the reference type data, each modification generates a new object, which avoids the situation that the sub components are not updated when the reference type data is transmitted.

When I first came into contact with react, the biggest feeling is that its degree of freedom is really high. All contents can be set according to my preferences, but it also increases the learning cost of beginners. (however, pay and harvest are in direct proportion. Continue my road of redemption!)

summary

  1. Class components: shouldComponentUpdate() and react Purecomponent can play an optimization role in basic type data transmission. shouldComponentUpdate() is more appropriate when reference type data transmission is included.
    Export method: export default react memo(Child).

Running the program again, we can see that although the parent component modified the count value, the child component skipped rendering.

Here I use the writing method of React hooks. When useState in hooks modifies the reference type data, each modification generates a new object, which avoids the situation that the sub components are not updated when the reference type data is transmitted.

When I first came into contact with react, the biggest feeling is that its degree of freedom is really high. All contents can be set according to my preferences, but it also increases the learning cost of beginners. (however, pay and harvest are in direct proportion. Continue my road of redemption!)

summary

  1. Class components: shouldComponentUpdate() and react Purecomponent can play an optimization role in basic type data transmission. shouldComponentUpdate() is more appropriate when reference type data transmission is included.
  2. Function component: use react memo.

Topics: Javascript React