美文网首页
setState是同步还是异步

setState是同步还是异步

作者: 糖糖不加糖_ | 来源:发表于2022-11-30 16:03 被阅读0次

    setState方法是同步还是异步

    在react源码中他是同步的方法,通过队列的形式更新state的值,因此展现给人是异步更新的状态,但实际上它是一个同步的方法

    setState两种传值格式

    • 传一个对象,此时当多个setState同时存在时会使用Object.assign方式进行合并,即若出现相同的key值,后面的value会覆盖前面的,最后统一执行合并完的事件

    Object.assign() 方法将所有可枚举Object.propertyIsEnumerable() 返回 true)的自有Object.hasOwnProperty() 返回 true)属性从一个或多个源对象复制到目标对象,返回修改后的对象。

    setState({  index: 1  })
    
    • 传一个方法,采用一个链式调用的方式,将前一个执行结果state作为后面setState的参数prevState,依次进行调用
    setState((prevState, props) => {
    // 获取上一个prevState做值操作
    return {XX: prevState.XX + 1}
    })
    

    两种类型的调用方式(对象方式、函数方式)混用

    两种方式不能混用,否则合并后就会出问题了

    const root = ReactDOM.createRoot(document.getElementById('root'));
    class Clock extends React.Component {
      state = {
        index: 0,
      };
    
      handleClick = () => {
          this.setState({index: this.index + 2});
          console.log('=========0,', this.state.index);
          this.setState((prev, props) => ({index: prev.index + 1}), () => {console.log('函数更新后-------0', this.state.index)});
          console.log('=========1,', this.state.index);
      };
    
      render() {
        console.log('======render', this.state.index);
        return <button onClick={this.handleClick}>Hello, world!</button>;
      }
    }
    root.render(<Clock  />)
    

    点击button后呈现结果


    image.png

    原理

    image.png

    具体操作为:
    1、首先将setState中的参数partialState存储到state暂存队列中
    2、判断当前是否处于批量处理状态,是,则将组件推入待更新队列,不是,则使用更新批量处理状态为ture,然后再将组件推入待更新队列(dirtyComponents)(批量更新完成后,设置批量处理状态为false,执行事务步骤)
    3、调用事务(Transaction)waper方法遍历组件待更新队列dirtyComponents,执行更新
    4、componentWillRecevieProps执行
    5、state暂存队列合并,获取最终要更新的state,队列置空(ps:函数参数也是在这里确定值获取到prevState)

     Object.assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
    

    6、componentShouldUpdate执行,根据返回值判断是否更新
    7、componentWillUpdate执行
    8、render执行
    9、componentDidUpdate执行

    最简单的话来讲就是state接收到一个新状态不会被立即执行,而是会被推入到pendingState队列(Queue)中,随后判断isBatchedUpdates的值,为true,则将新状态保存到dirtyComponents(脏组件)中;为false的话,遍历dirtyComponents,并调用updateComponent方法更新pengdingState中的state或props,将队列初始化

    React 的更新是基于 Transaction(事务)的,Transacation 就是给目标执行的函数包裹一下,加上前置和后置的 hook ,在开始执行之前先执行 initialize hook,结束之后再执行 close hook,这样搭配上 isBatchingUpdates 这样的布尔标志位就可以实现一整个函数调用栈内的多次 setState 全部入 pending 队列,结束后统一 apply 了。
    但是【setTimeout】这样的方法执行是脱离了事务的,react 管控不到,所以就没法 batch 了。
    原文链接:https://blog.csdn.net/qq_39207948/article/details/113803273

    总结

    image.png

    样例

    1、在同步函数中的setState

    在对象中的写法

    
    class Clock extends React.Component {
      state = {
        index: 0,
      };
    
      handleClick = () => {
          console.log('=========0,', this.state.index);
          this.setState({
            index: this.state.index + 1,
          }, () => {console.log('函数更新后-------0', this.state.index)});
          console.log('=========1,', this.state.index);
          this.setState({
            index: this.state.index + 5,
          }, () => {console.log('函数更新后-------1', this.state.index)});
          console.log('=========2,', this.state.index);
      };
     
     render() {
        console.log('======render', this.state.index);
        return <button onClick={this.handleClick}>Hello, world!</button>;
      }
    }
    ReactDOM.render(
      <Clock/>,
      document.getElementById('root')
    );
    
    image.png

    点击button,此时,setState将设置的值推到队列里等待执行,发现有两个待更新的setState,因此在队列里增加这两个值,等待没有其他的setState后一起执行,此时同步事件的console.log已经执行过了,因此提前输出了值,随后在确认没有其他的setState时,进行render,render后再去执行两个setState的回调函数,因为有推送到队列的操作,又是对象数据,在源码内部会调用Object.assign,将对象合并,此时有同属性key值的就会被合并到一起,后面的值会覆盖前面的值,因此最后输出的是5

    在函数中的写法

    class Clock extends React.Component {
      state = {
        index: 0,
      };
    
      handleClick = () => {
          console.log('=========0,', this.state.index);
          this.setState((prev, props) => ({index: prev.index + 1}), () => {console.log('函数更新后-------0', this.state.index)});
          console.log('=========1,', this.state.index);
          this.setState((prev, props) => ({index: prev.index + 5}), () => {console.log('函数更新后-------1', this.state.index)});
          console.log('=========2,', this.state.index);
      };
     
     render() {
        console.log('======render', this.state.index);
        return <button onClick={this.handleClick}>Hello, world!</button>;
      }
    }
    ReactDOM.render(
      <Clock/>,
      document.getElementById('root')
    );
    
    image.png

    点击button,两个setState同样会被推入队列中,因此跟上面一样,也会是在console.log执行完毕后才会更新state的值,即render,不同的是,此时render出来的结果是6,因为setState中使用的是函数,因此不会和对象一样使用Object.assign做合并处理,它本身不会合并,而是使用setState中更新后获取到的state值去传递给下一个setState函数,然后在进行计算,因此得到的值为6

    2、在异步函数或原生dom事件中的setState

    • 在16及16以下中的setState
      在对象中的写法
    class Clock extends React.Component {
      state = {
        index: 0,
      };
    
      handleClick = () => {
        setTimeout(() => {
          console.log('=========0,', this.state.index);
          this.setState({
            index: this.state.index + 1,
          }, () => {console.log('函数更新后-------0', this.state.index)});
          console.log('=========1,', this.state.index);
          this.setState({
            index: this.state.index + 5,
          }, () => {console.log('函数更新后-------1', this.state.index)});
          console.log('=========2,', this.state.index);
        });
      };
    
     render() {
        console.log('======render', this.state.index);
        return <button onClick={this.handleClick}>Hello, world!</button>;
      }
    }
    ReactDOM.render(
      <Clock/>,
      document.getElementById('root')
    );
    
    
    image.png

    点击button后,看到结果为下图,即每次调用setState后都会去render,render后立即调用setState的callback函数。在异步函数中能够同步执行,能获取到上一次setState后执行的结果去作为已知值更新当前的值

    在函数写法中参见下方

    class Clock extends React.Component {
      state = {
        index: 0,
      };
    
      handleClick = () => {
         setTimeout(() => {
          console.log('=========0,', this.state.index);
          this.setState((prev, props) => ({index: prev.index + 1}), () => {console.log('函数更新后-------0', this.state.index)});
          console.log('=========1,', this.state.index);
          this.setState((prev, props) => ({index: prev.index + 5}), () => {console.log('函数更新后-------1', this.state.index)});
          console.log('=========2,', this.state.index);
        });
      };
    
     render() {
        console.log('======render', this.state.index);
        return <button onClick={this.handleClick}>Hello, world!</button>;
      }
    }
    ReactDOM.render(
      <Clock/>,
      document.getElementById('root')
    );
    
    image.png

    由上图结果可见,在react16中,函数式结果和对象式结果一致

    • 18中的setState
      在对象中的写法
    const root = ReactDOM.createRoot(document.getElementById('root'));
    
    class Clock extends React.Component {
      state = {
        index: 0,
      };
    
      handleClick = () => {
         setTimeout(() => {
          console.log('=========0,', this.state.index);
          this.setState({
            index: this.state.index + 1,
          }, () => {console.log('函数更新后-------0', this.state.index)});
          console.log('=========1,', this.state.index);
          this.setState({
            index: this.state.index + 5,
          }, () => {console.log('函数更新后-------1', this.state.index)});
          console.log('=========2,', this.state.index);
        });
      };
    
      render() {
        console.log('======render', this.state.index);
        return <button onClick={this.handleClick}>Hello, world!</button>;
      }
    }
    
    root.render(<Clock  />)
    
    image.png

    点击button,setTimeout中的setState也可以同步执行,但是是批量执行了,将this.setState需要更新的对象合并到了一起,因此只取了index:this.state.index + 5的那个赋值,前面的赋值被覆盖掉了,但是render只会更新一次,就是在批量处理完数据,最后更新的时候覆盖的

    在函数写法中参见下方

    const root = ReactDOM.createRoot(document.getElementById('root'));
    
    class Clock extends React.Component {
      state = {
        index: 0,
      };
    
      handleClick = () => {
          setTimeout(() => {
          console.log('=========0,', this.state.index);
          this.setState((prevState, props) => ({index: prevState.index + 1}),
               () => {console.log('函数更新后-------0', this.state.index)}
          );
          console.log('=========1,', this.state.index);
          this.setState((prevState, props) => ({index: prevState.index + 5}), 
               () => {console.log('函数更新后-------1', this.state.index)}
          );
          console.log('=========2,', this.state.index);
        });
      };
    
      render() {
        console.log('======render', this.state.index);
        return <button onClick={this.handleClick}>Hello, world!</button>;
      }
    }
    
    root.render(<Clock  />)
    
    image.png

    点击button,会发现,他会先执行内部的同步方法,打印所有console.log后,也对setState进行了批量执行,因为最后只输出了一个render变量,但是与【对象写法】不同的是最后输出的结果是6,属于在setTimeout中将两个setState函数方法全都执行了,后面的方法执行拿到了上面的结果index,然后用第一个得到的index对下一个setState方法做值的处理,只是等所有值都更新完了,他在去进行render

    • 17版本默认是与16版本做同样的处理,但是,增加了unstable_batchedUpdates,她的作用是使setState批量处理内部方法,同react18呈现同样效果使用方式大体如下

    在对象中的写法

    class Clock extends React.Component {
      state = {
        index: 0,
      };
     handleClick = () => {
        setTimeout(() => {
          ReactDOM.unstable_batchedUpdates(() => {
            console.log('=========0,', this.state.index);
          this.setState({
            index: this.state.index + 1,
          }, () => {console.log('函数更新后-------0', this.state.index)});
          console.log('=========1,', this.state.index);
          this.setState({
            index: this.state.index + 5,
          }, () => {console.log('函数更新后-------1', this.state.index)});
          console.log('=========2,', this.state.index);
          })
      });
      };
    
     render() {
        console.log('======render', this.state.index);
        return <button onClick={this.handleClick}>Hello, world!</button>;
      }
    }
    ReactDOM.render(
      <Clock/>,
      document.getElementById('root')
    );
    
    image.png

    在函数写法中参见下方

    class Clock extends React.Component {
      state = {
        index: 0,
      };
    
     handleClick = () => {
        setTimeout(() => {
          ReactDOM.unstable_batchedUpdates(() => {
            console.log('=========0,', this.state.index);
            this.setState((prevState, props) => ({index: prevState.index + 1}),
                 () => {console.log('函数更新后-------0', this.state.index)}
            );
            console.log('=========1,', this.state.index);
            this.setState((prevState, props) => ({index: prevState.index + 5}), 
                 () => {console.log('函数更新后-------1', this.state.index)}
            );
            console.log('=========2,', this.state.index);
          })
      });
      };
    
     render() {
        console.log('======render', this.state.index);
        return <button onClick={this.handleClick}>Hello, world!</button>;
      }
    }
    ReactDOM.render(
      <Clock/>,
      document.getElementById('root')
    );
    
    image.png

    react测试链接:https://codepen.io/gaearon/pen/zKRGpo?editors=0010
    上述代码均可在其中运行

    事件循环event loop
    setTimeout和dom事件都属于js线程中的异步操作

    componentDidMount() {
      const btnEl = document.getElementById("btn");
    // dom事件
      btnEl.addEventListener('click', () => {
        this.setState({
          count: 1
        });
      })
    }
    

    参考文献:
    https://juejin.cn/post/6844903781813993486
    https://blog.csdn.net/qq_39207948/article/details/113803273
    https://www.51cto.com/article/711386.html

    相关文章

      网友评论

          本文标题:setState是同步还是异步

          本文链接:https://www.haomeiwen.com/subject/zkpzxdtx.html