React setState 同步 or 异步,合并 or 不合并

10 篇文章 0 订阅

我们在面试中经常会遇到一下的问题:

class Example extends React.Component{
    constructor(){
    super(...arguments)
        this.state = {
            count: 0
        };
    }
    componentDidMount(){
       // a
      this.setState({count: this.state.count + 1});
      console.log('1:' + this.state.count)
      // b
      this.setState({count: this.state.count + 1});
      console.log('2:' + this.state.count)
      setTimeout(() => {
        // c
        this.setState({count: this.state.count + 1});
        console.log('3:' + this.state.count)
      }, 0)
      // d
      this.setState(preState => ({ count: preState.count + 1 }), () => {
        console.log('4:' + this.state.count)
      })
      console.log('5:' + this.state.count)
      // e
      this.setState(preState => ({ count: preState.count + 1 }))
      console.log('6:' + this.state.count)
    }
}

思考一下,你的答案是什么???

 

 

你的答案是否正确?你又是否理解为什么会出现上面的答案?接下来我们就来仔细分析一下。

 

首先我们需要关注 setState 这个API,这个可以说是 React 中最重要的一个 API。

setState(updater, [callback])

setState 可以接受两个参数,第一个参数可以是一个对象或者是一个函数,都是用来更新 state。第二个参数是一个回调函数(相当于Vue中的$NextTick ),我们可以在这里拿到更新的 state。

 

在上面的代码中,【a,b,c】的 setState 的第一个参数都是一个对象,【e,f】的 setState 的第一个参数都是函数。

 

首先,我们先说说执行顺序的问题。

【1,2,5,6】最先打印,【4】在中间,最后打印【3】。因为【1,2,5,6】是同步任务,【4】是回调,相当于 NextTick 微任务,会在同步任务之后执行,最后的【3】是宏任务,最后执行。

 

接下来说说打印的值的问题。

在【1,2,5,6】下面打印的 state 都是0,说明这里是异步的,没有获取到即时更新的值;

在【4】里面为什么打印出3呢?

首先在【a,b】两次 setState 时,都是直接获取的 this.state.count 的值,我们要明白,这里的这个值有“异步”的性质(这里的“异步”我们后面还会讲到),异步就意味着这里不会拿到能即时更新的值,那每次 setState 时,拿到的 this.state.count 都是0。

在【d,e】两个 setState 时,它的参数是函数,这个函数接收的第一个参数 preState (旧的 state ),在这里是“同步”的,虽有能拿到即时更新的值,那么经过【a,b】两次 setState (这里类似于被合并),这里即时的 count 还是1。因为上面我们说过的执行顺序的关系,再经过【d,e】两次 setState ,所以 count 变成了3。

那么在【3】中打印出4又是为什么?你不是说了在 this.state.count 中拿到的值是“异步”的吗,不是应该拿到0吗,怎么会打印出4呢?

 

那么要说明白这个问题,我们就得来聊聊 react 的 batchUpdate 机制了。

我们先看下面的图,可以简单理解为在 setState 时,如果处于 batch update 就走左边的流程(异步,拿不到即时的值),如果不是就走右边的流程(同步,能拿到即时的值)。那么怎么判断是否处于 batch update 呢?

 

 

React 的 batchUpdate 机制会在每一个方法执行之前设置一个 isBatchingUpdate 为 true,在方法执行结束之后设置 isBatchingUpdate 为 false 。那么当在执行 setState 这句代码的时候,如果 isBatchingUpdate 是 true,就命中了 batchUpdate 机制,会进行 “异步更新”;反之则是 “同步更新”。

 

method() {
    isBatchingUpdate = true;
    // 你需要执行的一些代码
    // ...
    isBatchingUpdate = false
}

那么在上面的那个面试题中,在 setTimeout 执行的时候 isBatchingUpdate 是 false ,没有命中 batchUpdate 机制,所有同步更新,这里的 this.state.count 已经是 3 了,所有在【3】中打印的就是 4。

componentDidMount(){
    isBatchingUpdate = true
    setTimeout(() => {
      // c
      // 由于执行顺序的原因,在这里 isBatchingUpdate 已经是 false 了,所以同步更新
      this.setState({count: this.state.count + 1});
      console.log('3:' + this.state.count)
    }, 0)
    isBatchingUpdate = false
}

以上是这个面试题的问题。还有一些 react 中自定义的 DOM 事件,同样是异步代码,也遵循这个 batchUpdata 机制,明白了这其中的原理,啥面试题都难不住我们。

 

那么接下来我们做下总结:

  1. this.state是否异步,关键是看是否命中 batchUpdata 机制,命中就异步,未命中就同步。
  2. setState 中的 preState 参数,总是能拿到即时更新(同步)的值。

搞明白上面这两条,一切就都解决了。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值