关于前端:setState同步还是异步

本文基于React17.0.0

setState的执行流程

简略来说,setState函数是React.Component类的一个办法,它记录了以后利用产生的更新update,并把这些update用一个队列updateQueue记录下来,而后进入scheduleUpdateOnFiber这个函数中进行调度更新,而后触发performSyncWorkOnRoot函数来进入render阶段。最初在commit阶段把更新渲染到dom上。

同步还是异步

为了更加具体的了解setState的执行行为,咱们先来看上面的代码,预设咱们是在ReactDOM.renderlegacy模式下运行,你能够正确说出两次console.log的后果吗?

constructor(props) {
    super(props);
    this.state = {
      data: "hello"
    }
  }
componentDidMount() {
    //第一个setState
    this.setState({
      data: 'world'
    })
    
    console.log("in componentDidMount: ", this.state.data);

    setTimeout(() => {
      // 第二个setState
      this.setState({
        data: 'Bob'
      })

      console.log("in setTimeout: ", this.state.data);
    })
  }

揭晓答案,后果是

咱们能够看到,第一个log输入的是hello,也就是说,当执行第一个setState时,是体现为同步行为的,在执行第二个setState时,log输入的是Bob,是体现为异步行为的。

那么这是为什么呢,能够粗略看出,两个setState执行的环境是不同的,第一个setState的执行环境是在componentDidMount()这个生命周期钩子函数中,第二个setState是在setTimeout中执行的。置信大家一眼就看进去了,问题的实质就是执行环境的不同,导致了setState在执行时的上下文就不一样了,而执行上下文executionContext,就是决定setState同步异步行为的要害。

为何executionContext为何在这个过程中这么重要呢,咱们在后面理解到setState在执行过程中会进入 scheduleUpdateOnFiber这个函数,咱们先来简略看下在React源码中,有什么蹊跷吧。

scheduleUpdateOnFiber

function scheduleUpdateOnFiber(fiber, lane, eventTime) {
  // 省略 .....
  
  if (lane === SyncLane) {// ReactDOM.render 同步执行
    if ( // Check if we're inside unbatchedUpdates
    (executionContext & LegacyUnbatchedContext) !== NoContext && // Check if we're not already rendering
    (executionContext & (RenderContext | CommitContext)) === NoContext) {
      // Register pending interactions on the root to avoid losing traced interaction data.
      schedulePendingInteractions(root, lane); // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
      performSyncWorkOnRoot(root);
    } else {
      ensureRootIsScheduled(root, eventTime);
      schedulePendingInteractions(root, lane);

      if (executionContext === NoContext) {
        resetRenderTimer();
        flushSyncCallbackQueue();
      }
    }
  } else {
    // 省略 .....
   } 

    ensureRootIsScheduled(root, eventTime);
    schedulePendingInteractions(root, lane);
  } 
  mostRecentlyUpdatedRoot = root;
} 

咱们能够看到其中的要害代码

if (executionContext === NoContext) {
        resetRenderTimer();
        flushSyncCallbackQueue();
}

其中flushSyncCallbackQueue函数的作用就是同步执行更新队列里的更新update,后面咱们说过了setState后会把更新对象update以队列模式保留下来,这里就是要执行这些更新并清空队列。
咱们能够看出,如果以后的执行上下文executionContextNoContext,也就是说明React曾经不处于本人的调度环节了,而是处于一种无事可做的状态时,React就会去同步的执行setState的回调函数进行更新。
那么,什么时候才会是一种无事可做的状态呢?

答案就是不处于React自身的调度阶段时,比方setTimeout,网络申请,间接在Dom节点上绑定的事件等,这些行为都不会触发React的调度行为。当React处于本人的调度阶段时,会依据所处的状态不同给executionContext赋值不同的值,如赋值为BatchedContext时阐明进入了更新合并阶段,而executionContext默认状况下就是NoContext。所以不在调度阶段时,React就会进入无事可做的状态,就会将setState同步执行。在React处于本人的调度阶段,会执行诸如生命周期钩子函数,合成事件等,在这些状况下,会触发batchedUpdate进行合并更新,所以此时将executionContext赋值为BatchedContext,那么天然就是异步的行为了。

在咱们的例子中,componentDidMount属于React调度流程的一部分,所以其中的setState会被异步执行,componentDidMount执行完后,React就退出调度过程了,此时的executionContextNoContext 了,然而咱们的代码还没有执行完,因为setTimeout是一个异步办法,等它的回调被执行的时候,React就会以同步的形式执行setState了。

setState与useState

useState是React为函数组件赋予状态的一种做法,然而它和setState的调用栈有重合的中央,也就是都会进入scheduleUpdateOnFiber函数,所以在同步异步的行为上是没有区别的。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理