React中setState如何同步更新


一、说明

关于调用 setState() 进行状态更新,官方有如下说明

State Updates May Be Asynchronous

React may batch multiple setState() calls into a single update for performance.

Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.

译:

State 的更新可能是异步的

出于性能方面的考虑,React 可以将多次的 setState() 调用合并为一次

因为 this.props 和 this.state 可能是异步更新的,你不应该用它们当前的值去计算下一个 state 的值

在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates 判断是直接更新 this.state 还是放到队列中延时更新,而 isBatchingUpdates 默认是 false,表示 setState 会同步更新 this.state;但是,有一个函数 batchedUpdates,该函数会把 isBatchingUpdates 修改为 true,而当 React 在调用事件处理函数之前就会先调用这个 batchedUpdates将isBatchingUpdates修改为true,这样由 React 控制的事件处理过程 setState 不会同步更新 this.state。

二、setState何时同步何时异步?

由React控制的事件处理程序,以及生命周期函数调用setState不会同步更新state

React控制之外的事件中调用setState是同步更新的。比如原生js绑定的事件,setTimeout/setInterval等

大部分开发中用到的都是React封装的事件,比如onChange、onClick、onTouchMove等,这些事件处理程序中的setState都是异步处理的。

三、同步更新策略

1.完成回调

setState函数的第二个参数允许传入回调函数,在状态更新完毕后进行调用,譬如:

this.setState({
      load: !this.state.load,
      count: this.state.count + 1
    }, () => {
      console.log(this.state.count);
      console.log('加载完成')
    });

2.Promise封装结合async/await

其实这里就是JavaScript异步编程相关知识,将上面回调写法换成Promise

引入Promise来封装setState:

setStateAsync(state) {
    return new Promise((resolve) => {
      this.setState(state, resolve)
    });
  }

setStateAsync 返回的是Promise对象,在调用时我们可以使用Async/Await语法来优化代码风格:

  async componentDidMount() {
       await this.setStateAsync ({
               count: this.state.count * 1 + 2
           })
           console.log(123, this.state.count)
           await this.setStateAsync ({
               count: this.state.count * 1 + 2
           })
           console.log(456, this.state.count)
  }

3.直接使用操作异步函数async / await 

此时每次await setState都会执行一遍render方法

handleChange = async () => {
           await this.setState({
               count: this.state.count * 1 + 2
           })
           console.log(123, this.state.count)
           await this.setState({
               count: this.state.count * 1 + 2
           })
           console.log(456, this.state.count)
 }

四、setState参数详解

state中定义count初始值为1;

1.第一个参数为传入对象;

handleChange = () => {
        this.setState({
            count: this.state.count  + 2
        })
        console.log(123, this.state.count)
        this.setState({
            count: this.state.count  + 2
        })
        console.log(456, this.state.count)
    }

调用方法两次输出值为都为3;

 React 出于性能方面的考虑,并不会直接对每次的调用都进行更新,而是会将多次传入的对象进行合并处理,以产生一个新的最终的 state 对象;

第二个参数为完成回调

handleChange = () => {
        this.setState({
            count: this.state.count  + 2
        }, () => {
            console.log(123, this.state.count)
        })
        this.setState({
            count: this.state.count  + 2
        }, () => {
            console.log(456, this.state.count)
        })
    }

与上面输出保持一致

2.第一个参数为传入函数:

handleChange = () => {
        this.setState((prevState, props) => ({
            count: prevState.count  + 2
        }), () => {
            console.log(123, this.state.count)
        })
        this.setState((prevState, props) => ({
            count: prevState.count  + 2
        }), () => {
            console.log(456, this.state.count)
        })
    }

函数第一个参数接收先前的状态作为第一个参数,将此次更新被应用时的props做为第二个参数;

此时输出为两次都是5;

在第二次调用 setState 方法时便可以通过 prevState.count 拿到最新的值从而更新本次的 state 。显然,React 对于传入函数的方式和传入对象的方式进行更新 state 的各自具体理念是不一样的,对于传入函数的方式,在调用 setState 进行更新 state 时,React 会按照各个 setState 的调用顺序,将它们依次放入一个队列,然后,在进行状态更新时,则按照队列中的先后顺序依次调用,并将上一个调用结束时产生的 state 传入到下一个调用的函数中,当然,第一个 setState 调用时,传入的 prevState 则是当前的 state ,如此,便解决了传入对象式调用 setState 方法所存在的 不能依赖上一次的 state 去计算本次 state 的问题。

handleChange = () => {
        this.setState((prevState, props) => ({
            count: prevState.count  + 2
        }), () => {
            console.log(123, this.state.count)
        })
        this.setState((prevState, props) => ({
            count: this.state.count  + 2
        }), () => {
            console.log(456, this.state.count)
        })
    }

值得一提的是,在上面的这段代码中,执行第二个 setState 里面的函数时,由第一个 setState 所产生的最新的 state 并没有合并到 this 对象上面去,所以此时通过 this.state 获取不到最新的状态,故而在函数内部拿到的 this.state.count 的值为 1 而非 3。

另外,不论第一个参数是何种类型,多次setState也只会调用一次render方法,第二个参数callback完成回调中获取this.state的值同样也是多次setState合并操作后的最终值;

 

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM