Talk about the synchronization and asynchrony of setState() in React class components (attached test questions)

I Two ways to write setState() update state

  1. setState(updater, [callback]),
    updater is the function that returns the stateChange object: (state, props) = > stateChange
    The received state and props are guaranteed to be up-to-date
  2. setState(stateChange, [callback])
    stateChange as the object,
    Callback is an optional callback function, which is executed only after the status is updated and the interface is updated
  3. Summary:
    Object mode is short for function mode
    If the new state does not depend on the original state = = = > use object mode
    If the new state depends on the original state = = = > use function mode
    If you need to get the latest state data after setState(), read it in the second callback function
  4. example:
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>01_setState()Use of</title>
</head>
<body>

<div id="example"></div>

<script type="text/javascript" src="./js/react.development.js"></script>
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<script type="text/javascript" src="./js/babel.min.js"></script>
<script src="https://cdn.bootcss.com/remarkable/1.7.1/remarkable.min.js"></script>

<script type="text/babel">

  class A extends React.Component {

    state = {
      count: 1
    }

    test1 = () => {
      this.setState(state => ({count: state.count + 1}))
      console.log('test1 setState()after', this.state.count)
    }

    test2 = () => {
      /*const count = this.state.count + 1
      this.setState({
        count
      })*/
      this.setState({
        count: 3
      })
      console.log('test2 setState()after', this.state.count)
    }

    test3 = () => {
      this.setState(state => ({count: state.count + 1}), () => { // Callback after status update and interface update
        console.log('test3 setState callback()', this.state.count)
      })
    }

    render() {
      console.log('A render()')
      return (
        <div>
          <h1>A assembly: {this.state.count}</h1>
          <button onClick={this.test1}>A Test 1</button>&nbsp;&nbsp;
          <button onClick={this.test2}>A Test 2</button>&nbsp;&nbsp;
          <button onClick={this.test3}>A Test 3</button>&nbsp;&nbsp;
        </div>
      )
    }
  }

  ReactDOM.render(<A/>, document.getElementById('example'))
</script>
</body>
</html>




When we press buttons 1, 2 and 3 in turn, we will find that the event listening function of button 1 runs console first Log ('test1 after setState() ', this state. Count (), and then render(), and this Setstate (state = > ({count: state. Count + 1})) is the first sentence. Therefore, we can infer that setstate () is asynchronous. Similarly, buttons 2 and 3 are also asynchronous.
It is worth mentioning that this in button 3 Setstate (state = > ({count: state. Count + 1}), () = > {/ / callback after the state is updated and the interface is updated. There is a callback function in console.log('test3 setState callback()', this.state.count)}). In general, we use its short form (object form), setState() in the form of a function is used only when the latest state data is obtained after setState() is required.

II Is setState() updating state asynchronous or synchronous?

  1. Where to execute setState()?
    In the callback function controlled by react: life cycle hook / react event listening callback
    Asynchronous callback functions not controlled by react: timer callback / native event listening callback / promise callback /
  2. Asynchronous OR synchronous?
    In react related callbacks: asynchronous
    In other asynchronous callbacks: synchronous
  3. example
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>02_setState()asynchronous OR Synchronous update</title>
</head>
<body>

<div id="example"></div>

<script type="text/javascript" src="./js/react.development.js"></script>
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<script type="text/javascript" src="./js/babel.min.js"></script>

<script type="text/babel">
  class StateTest extends React.Component {

    state = {
      count: 0,
    }

    /*
     react In the event listening callback, setState() is the asynchronous update state
     */
    update1 = () => {
      console.log('update1 setState()before', this.state.count)
      this.setState(state => ({count: state.count + 1}))
      console.log('update1 setState()after', this.state.count)
    }

    /*
     react In the lifecycle hook, setState() is the asynchronous update state
     */
    componentDidMount () {
      console.log('componentDidMount setState()before', this.state.count)
      this.setState(state => ({count: state.count + 1}))
      console.log('componentDidMount setState()after', this.state.count)
    }

    /*
    Timer callback / native event listening callback / promise callback /
     */
    update2 = () => {
      setTimeout(() => {
        console.log('setTimeout setState()before', this.state.count)
        this.setState(state => ({count: state.count + 1}))
        console.log('setTimeout setState()after', this.state.count)
      })
    }
    update3 = () => {
      const h2 = this.refs.count
      h2.onclick = () => {
        console.log('onclick setState()before', this.state.count)
        this.setState(state => ({count: state.count + 1}))
        console.log('onclick setState()after', this.state.count)
      }
    }
    update4 = () => {
      Promise.resolve().then(value => {
        console.log('Promise setState()before', this.state.count)
        this.setState(state => ({count: state.count + 1}))
        console.log('Promise setState()after', this.state.count)
      })
    }


    update5 = () => {
      console.log('onclick setState()before', this.state.count)
      this.setState(state => ({count: state.count + 1}))
      console.log('onclick setState()after', this.state.count)
      console.log('onclick setState()Previous 2', this.state.count)
      this.setState(state => ({count: state.count + 1}))
      console.log('onclick setState()After 2', this.state.count)
    }

    update6 = () => {
      console.log('onclick setState()before', this.state.count)
      this.setState({count: this.state.count + 1})
      console.log('onclick setState()after', this.state.count)
      console.log('onclick setState()Previous 2', this.state.count)
      this.setState({count: this.state.count + 1})
      console.log('onclick setState()After 2', this.state.count)
    }

    update7 = () => {
      console.log('onclick setState()before', this.state.count)
      this.setState({count: this.state.count + 1})
      console.log('onclick setState()after', this.state.count)

      console.log('onclick setState()Previous 2', this.state.count)
      this.setState(state => ({count: state.count + 1}))
      console.log('onclick setState()After 2', this.state.count)
    }


    render() {
      const {count} = this.state
      console.log('render()', count)
      return (
        <div>
          <h2 ref='count'>{count}</h2>
          <button onClick={this.update1}>Update 1</button> ---
          <button onClick={this.update2}>Update 2</button> &nbsp;
          <button onClick={this.update3}>Update 3</button> &nbsp;
          <button onClick={this.update4}>Update 4</button> ---
          <button onClick={this.update5}>Update 5</button> &nbsp;
          <button onClick={this.update6}>Update 6</button> &nbsp;
          <button onClick={this.update7}>Update 7</button> &nbsp;
        </div>
      )
    }
  }

  ReactDOM.render(<StateTest/>, document.getElementById('example')) // When rendering the component label, the render() virtual DOM of the component label object will be called internally

</script>
</body>
</html>








I've put the conclusion at the beginning, but when updating 6, we found that we clearly wrote setState() twice in the code, but the final result was only updated once, while updating 5 also wrote setState() twice, but it was the result of two actions. Why?

III About asynchronous setState()

  1. How to handle multiple calls?
    setState({}): merge and update the state once, and call render() only once to update the interface - both state update and interface update are merged
    setState(fn): update the state many times, but only call render() once to update the interface - the state updates are not merged, but the interface updates are merged

  2. How to get the status data after asynchronous update?
    In the callback callback function of setState()

IV Interview questions

The left side of the arrow in the note is the order, and the right side is the printed value

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>03_setState()Interview questions</title>
</head>
<body>

<div id="example"></div>

<script type="text/javascript" src="./js/react.development.js"></script>
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<script type="text/javascript" src="./js/babel.min.js"></script>

<script type="text/babel">
  class StateTest extends React.Component {

    state = {
      count: 0,
    }

    componentDidMount() {
      this.setState({count: this.state.count + 1})
      this.setState({count: this.state.count + 1})
      console.log(this.state.count) // 2 ==> 0

      this.setState(state => ({count: state.count + 1}))
      this.setState(state => ({count: state.count + 1}))
      console.log(this.state.count) // 3 ==> 0

      setTimeout(() => {
        this.setState({count: this.state.count + 1})
        console.log('timeout', this.state.count) // 10 ==> 6

        this.setState({count: this.state.count + 1})
        console.log('timeout', this.state.count) // 12 ==> 7
      }, 0)

      Promise.resolve().then(value => {
        this.setState({count: this.state.count + 1})
        console.log('promise', this.state.count)  // 6 ==>4

        this.setState({count: this.state.count + 1})
        console.log('promise', this.state.count) // 8 ==> 5
      })
    }

    render() {
      const count = this.state.count
      console.log('render', count)  // 1 ==> 0   4 ==>3   5 ==>4  7 ==>5  9 ==>6  11 ==>7
      return (
        <div>
          <p>{count}</p>
        </div>
      )
    }
  }

  ReactDOM.render(<StateTest/>, document.getElementById('example'))

</script>
</body>
</html>


Keywords: Javascript Front-end React

Added by PHPSpirit on Tue, 08 Feb 2022 20:41:23 +0200