React 中的 setState 是微任务还是宏任务

2021-08-10  本文已影响0人  前端小白的摸爬滚打

首先这个问法就表明 setState 是一个异步的任务

  1. 和 setTimeout 以及 Promise.then 做比较

首先在 React 的事件处理函数中 & React 的生命周期中 的 setState 是不会立即执行的,然后我们就发现 setState 执行完毕的时间是早于 Promise.then 的,但是这并不能证明他就是一个同步任务

handleClick = () => {
  const fans = Math.floor(Math.random() * 10)
  setTimeout(() => {
    console.log('宏任务触发')
  })
  Promise.resolve().then(() => {
    console.log('微任务触发')
  })
  this.setState({
    count: this.state.count + fans
  }, () => {
    console.log('新增粉丝数:', fans)
  })
}
  1. 和同步的 console.log 做对比
handleClick = () => {
  const fans = Math.floor(Math.random() * 10)
  console.log('开始运行')
  this.setState({
    count: this.state.count + fans
  }, () => {
    console.log('新增粉丝数:', fans)
  })
  console.log('结束运行')
}

结果

开始运行
结束运行
新增粉丝数: xxx

这么看,似乎 setState 又是一个异步的操作。主要原因是,在 React 的生命周期以及绑定的事件流中,所有的 setState 操作会先缓存到一个队列中,在整个事件结束后或者 mount 流程结束后,才会取出之前缓存的 setState 队列进行一次计算,触发 state 更新。只要我们跳出 React 的事件流或者生命周期,就能打破 React 对 setState 的掌控。最简单的方法,就是把 setState 放到 setTimeout 的匿名函数中。

  1. 将 setState 放在定时器中
handleClick = () => {
  setTimeout(() => {
    const fans = Math.floor(Math.random() * 10)
    console.log('开始运行')
    this.setState({
      count: this.state.count + fans
    }, () => {
      console.log('新增粉丝数:', fans)
    })
    console.log('结束运行')
  })
}

结果:

开始运行
新增粉丝数:xxx
结束运行

所以,setState 就是一次同步行为.

React 是怎么控制 setState 的

React 在绑定事件时,会对事件进行合成,统一绑定到 document 上( react@17 有所改变,变成了绑定事件到 render 时指定的那个 DOM 元素),最后由 React 来派发。

所有的事件在触发的时候,都会先调用 batchedEventUpdates$1 这个方法,在这里就会修改 executionContext 的值,React 就知道此时的 setState 在自己的掌控中。

function scheduleUpdateOnFiber(fiber, lane, eventTime) {
  if (lane === SyncLane) {
    // 同步操作
    ensureRootIsScheduled(root, eventTime);
    // 判断当前是否还在 React 事件流中
    // 如果不在,直接调用 flushSyncCallbackQueue 更新
    if (executionContext === NoContext) {
      flushSyncCallbackQueue();
    }
  } else {
    // 异步操作
  }
}

判断了 executionContext 是否等于 NoContext 来确定当前更新流程是否在 React 事件流中。

所以,不管是直接调用 flushSyncCallbackQueue ,还是推迟调用,这里本质上都是同步的,只是有个先后顺序的问题。

Component.setState 方法最终会调用 enqueueSetState 方法,而 enqueueSetState 方法内部会调用 scheduleUpdateOnFiber 方法,区别就在于正常调用的时候,scheduleUpdateOnFiber 方法内只会调用 ensureRootIsScheduled ,在事件方法结束后,才会调用 flushSyncCallbackQueue 方法。而脱离 React 事件流的时候,scheduleUpdateOnFiber 在 ensureRootIsScheduled 调用结束后,会直接调用 flushSyncCallbackQueue 方法,这个方法就是用来更新 state 并重新进行 render

结论: setState 是同步的

上一篇下一篇

猜你喜欢

热点阅读