React performance optimization SCU

1. Concept

shouldComponentUpdate is referred to as SCU, which is an important part of performance optimization in React.

shouldComponentUpdate(nextProps, nextState) {<!-- -->
    // Determine whether it needs to be rendered, if so, return true, otherwise return false
    if (nextProps.b === this.props.b) {<!-- -->
        return false;
    } else {<!-- -->
        return true;
    }
}

2. Usage scenarios

If one of all child components of the same parent component in react is updated, all remaining child components will be re-rendered, but for the sake of performance, we do not need to update components that have not updated their dependencies. SCU is to solve the problem of re-rendering components whose dependencies have not been updated.

3. SCU actual combat (shallow comparison is applicable to most situations, try not to compare in depth)

1. Basic usage

example 1:

import React from 'react'

class App extends React.Component {<!-- -->
  constructor(props) {<!-- -->
      super(props)
      this.state = {<!-- -->
          count: 0
      }
  }
  render() {<!-- -->
      return <div>
          <span>{<!-- -->this.state.count}</span>
          <button onClick={<!-- -->this.onIncrease}>increase</button>
      </div>
  }
  onIncrease = () => {<!-- -->
      this.setState({<!-- -->
          count: this.state.count + 1
      })
  }
  // Demonstrate the basic use of shouldComponentUpdate
  shouldComponentUpdate(nextProps, nextState) {<!-- -->
      if (nextState.count !== this.state.count) {<!-- -->
          return true // can be rendered
      }
      return false // Do not render repeatedly
  }
}

export default App

example 1:

import React from 'react'
import PropTypes from 'prop-types'

class Input extends React.Component {<!-- -->
    constructor(props) {<!-- -->
        super(props)
        this.state = {<!-- -->
            title: ''
        }
    }
    render() {<!-- -->
        return <div>
            <input value={<!-- -->this.state.title} onChange={<!-- -->this.onTitleChange}/>
            <button onClick={<!-- -->this.onSubmit}>Submit</button>
        </div>
    }
    onTitleChange = (e) => {<!-- -->
        this.setState({<!-- -->
            title:e.target.value
        })
    }
    onSubmit = () => {<!-- -->
        const {<!-- --> submitTitle } = this.props
        submitTitle(this.state.title) // 'abc'

        this.setState({<!-- -->
            title: ''
        })
    }
}
//props type checking
Input.propTypes = {<!-- -->
    submitTitle: PropTypes.func.isRequired
}

class List extends React.Component {<!-- -->
    constructor(props) {<!-- -->
        super(props)
    }
    render() {<!-- -->
        const {<!-- --> list } = this.props

        return <ul>{<!-- -->list.map((item, index) => {<!-- -->
            return <li key={<!-- -->item.id}>
                <span>{<!-- -->item.title}</span>
            </li>
        })}</ul>
    }
}
//props type checking
List.propTypes = {<!-- -->
    list: PropTypes.arrayOf(PropTypes.object).isRequired
}

class Footer extends React.Component {<!-- -->
    constructor(props) {<!-- -->
        super(props)
    }
    render() {<!-- -->
        return <p>
            {<!-- -->this.props.text}
            {<!-- -->this.props.length}
        </p>
    }
    componentDidUpdate() {<!-- -->
        console.log('footer did update')
    }
    shouldComponentUpdate(nextProps, nextState) {<!-- -->
      console.log(nextProps, this.props)
        if (nextProps.text !== this.props.text) {<!-- --> // || nextProps.length !== this.props.length
          return true // can be rendered
        }
        return false // Do not render repeatedly
    }

    // React default: If the parent component is updated, the child component will also be updated unconditionally! ! !
    // Performance optimization is even more important for React!
    // Does SCU have to be used every time? --Optimize only when necessary
}

class TodoListDemo extends React.Component {<!-- -->
    constructor(props) {<!-- -->
        super(props)
        //Status (data) promotion
        this.state = {<!-- -->
            list: [
                {<!-- -->
                    id: 'id-1',
                    title: 'Title 1'
                },
                {<!-- -->
                    id: 'id-2',
                    title: 'Title 2'
                },
                {<!-- -->
                    id: 'id-3',
                    title: 'Title 3'
                }
            ],
            footerInfo: 'Bottom text'
        }
    }
    render() {<!-- -->
        return <div>
            <Input submitTitle={<!-- -->this.onSubmitTitle}/>
            <List list={<!-- -->this.state.list}/>
            <Footer text={<!-- -->this.state.footerInfo} length={<!-- -->this.state.list.length}/>
        </div>
    }
    onSubmitTitle = (title) => {<!-- -->
        this.setState({<!-- -->
            list: this.state.list.concat({<!-- -->
                id: `id-${<!-- -->Date.now()}`,
                title
            })
        })
    }
}

export defaultTodoListDemo

Example 3:

import React from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'

class Input extends React.Component {<!-- -->
    constructor(props) {<!-- -->
        super(props)
        this.state = {<!-- -->
            title: ''
        }
    }
    render() {<!-- -->
        return <div>
            <input value={<!-- -->this.state.title} onChange={<!-- -->this.onTitleChange}/>
            <button onClick={<!-- -->this.onSubmit}>Submit</button>
        </div>
    }
    onTitleChange = (e) => {<!-- -->
        this.setState({<!-- -->
            title:e.target.value
        })
    }
    onSubmit = () => {<!-- -->
        const {<!-- --> submitTitle } = this.props
        submitTitle(this.state.title)

        this.setState({<!-- -->
            title: ''
        })
    }
}
//props type checking
Input.propTypes = {<!-- -->
    submitTitle: PropTypes.func.isRequired
}

class List extends React.Component {<!-- -->
  constructor(props) {<!-- -->
      super(props)
  }
  render() {<!-- -->
    const {<!-- --> list } = this.props

    return <ul>{<!-- -->list.map((item, index) => {<!-- -->
        return <li key={<!-- -->item.id}>
            <span>{<!-- -->item.title}</span>
        </li>
    })}</ul>
  }
  componentDidUpdate() {<!-- -->
    console.log('List did update')
  }

  // Add shouldComponentUpdate
  shouldComponentUpdate(nextProps, nextState) {<!-- -->
    console.log(nextProps.list, this.props.list)
      // _.isEqual does deep comparison of objects or arrays (recursively to the end at one time)
      if (_.isEqual(nextProps.list, this.props.list)) {<!-- -->
          // If equal, no repeated rendering
          return false
      }
      return true // If not equal, render
  }
}
//props type checking
List.propTypes = {<!-- -->
    list: PropTypes.arrayOf(PropTypes.object).isRequired
}

class TodoListDemo extends React.Component {<!-- -->
  constructor(props) {<!-- -->
      super(props)
      this.state = {<!-- -->
          list: [
              {<!-- -->
                  id: 'id-1',
                  title: 'Title 1'
              },
              {<!-- -->
                  id: 'id-2',
                  title: 'Title 2'
              },
              {<!-- -->
                  id: 'id-3',
                  title: 'Title 3'
              }
          ]
      }
  }
  render() {<!-- -->
      return <div>
          <Input submitTitle={<!-- -->this.onSubmitTitle}/>
          <List list={<!-- -->this.state.list}/>
      </div>
  }
  onSubmitTitle = (title) => {<!-- -->
      // Correct usage
      this.setState({<!-- -->
          list: this.state.list.concat({<!-- -->
              id: `id-${<!-- -->Date.now()}`,
              title
          })
      })

      // // In order to demonstrate SCU, the wrong usage was deliberately written. SCU must be used with immutable values.
      // this.state.list.push({ // It has been changed and setState will be the same nextProps === this.props. If SCU returns false, it will not be updated.
      // id: `id-${Date.now()}`,
      // title
      // })
      // this.setState({<!-- -->
      // list: this.state.list
      // })
  }
}

export default TodoListDemo

2. PureComponent method (class component)

When a class component extends pureComponent, the current component is also a scu of Shallow Comparison

example 1:

import React from 'react'
import PropTypes from 'prop-types'

class Input extends React.Component {<!-- -->
  constructor(props) {<!-- -->
      super(props)
      this.state = {<!-- -->
          title: ''
      }
  }
  render() {<!-- -->
    return <div>
      <input value={<!-- -->this.state.title} onChange={<!-- -->this.onTitleChange}/>
      <button onClick={<!-- -->this.onSubmit}>Submit</button>
    </div>
  }
  onTitleChange = (e) => {<!-- -->
    this.setState({<!-- -->
        title:e.target.value
    })
  }
  onSubmit = () => {<!-- -->
    const {<!-- --> submitTitle } = this.props
    submitTitle(this.state.title)

    this.setState({<!-- -->
        title: ''
    })
  }
}
//props type checking
Input.propTypes = {<!-- -->
  submitTitle: PropTypes.func.isRequired
}

class List extends React.PureComponent {<!-- -->
  constructor(props) {<!-- -->
      super(props)
  }
  componentDidUpdate() {<!-- -->
    console.log('List did update')
  }
  render() {<!-- -->
    const {<!-- --> list } = this.props

    return <ul>{<!-- -->list.map((item, index) => {<!-- -->
        return <li key={<!-- -->item.id}>
            <span>{<!-- -->item.title}</span>
        </li>
    })}</ul>
  }
  // shouldComponentUpdate() {/*Shallow comparison*/} In fact, it is equivalent to adding this to do shallow comparison
}
//props type checking
List.propTypes = {<!-- -->
  list: PropTypes.arrayOf(PropTypes.object).isRequired
}

class TodoListDemo extends React.Component {<!-- -->
  constructor(props) {<!-- -->
    super(props)
    this.state = {<!-- -->
      list: [
          {<!-- -->
              id: 'id-1',
              title: 'Title 1'
          },
          {<!-- -->
              id: 'id-2',
              title: 'Title 2'
          },
          {<!-- -->
              id: 'id-3',
              title: 'Title 3'
          }
      ]
    }
  }
  render() {<!-- -->
    return <div>
        <Input submitTitle={<!-- -->this.onSubmitTitle}/>
        <List list={<!-- -->this.state.list}/>
    </div>
  }
  onSubmitTitle = (title) => {<!-- -->
    // Correct usage
    this.setState({<!-- -->
        list: this.state.list.concat({<!-- -->
            id: `id-${<!-- -->Date.now()}`,
            title
        })
    })

    // // Deliberately written incorrect usage in order to demonstrate SCU
    // this.state.list.push({<!-- -->
    // id: `id-${Date.now()}`,
    // title
    // })
    // this.setState({<!-- -->
    // list: this.state.list
    // })
  }
}

export default TodoListDemo

3. React.memo high-order component (function component)

The high-order function of React.memo can be called a high-order component
The high-order component React.memo is used for scu, which is also a shallow comparison scu.

4. Summary of performance optimization

immutable.js