本文基于React17.0.0setState的执行流程。简单的说,setState函数就是React.Component类的一个方法,记录当前应用中发生的update更新,并用一个队列updateQueue记录这些更新,然后进入scheduleUpdateOnFiber函数进行调度更新,然后触发performSyncWorkOnRoot函数进入渲染阶段。最后,更新在提交阶段呈现给dom。同步or异步为了更具体的理解setState的执行行为,我们先看下面的代码。假设我们在ReactDOM.render的遗留模式下运行。你能正确地说出两次console.log的结果吗?构造函数(道具){超级(道具);this.state={data:"hello"}}componentDidMount(){//首先设置状态this.setState({data:'world'})console.log("incomponentDidMount:",this.state.data);setTimeout(()=>{//第二个setStatethis.setState({data:'Bob'})console.log("insetTimeout:",this.state.data);})}揭示答案,结果是那我们可以看到第一个log的输出是hello,也就是说,当第一个setState执行的时候,表现为同步行为,而当第二个setState执行的时候,log输出的是Bob,也就是异步行为。那么这是为什么呢?大致可以看出,这两个setState的执行环境是不一样的。第一个setState的执行环境在componentDidMount()的生命周期钩子函数中,第二个setState在setTimeout中执行。.相信大家一眼就能看出来。问题的本质是执行环境的不同,导致执行setState时上下文不同。执行上下文是决定setState同步和异步行为的关键。为什么executionContext在这个过程中如此重要?前面我们了解到setState在执行过程中会进入scheduleUpdateOnFiber函数。让我们简单地看一下React的源代码,看看有什么奇怪的。scheduleUpdateOnFiberfunctionscheduleUpdateOnFiber(fiber,lane,eventTime){//省略.....if(lane===SyncLane){//ReactDOM.render同步执行if(//检查我们是否在unbatchedUpdates(executionContext&LegacyUnbatchedContext)!==NoContext&&//检查我们是否尚未渲染(executionContext&(RenderContext|CommitContext))===NoContext){//在根上注册挂起的交互以避免丢失跟踪的交互数据。schedulePendingInteractions(根,车道);//这是一个遗留的边缘案例。ReactDOM.render的初始挂载performSyncWorkOnRoot(root);}else{ensureRootIsScheduled(root,eventTime);schedulePendingInteractions(根,车道);if(executionContext===NoContext){resetRenderTimer();flushSyncCallbackQueue();}}}else{//省略.....}ensureRootIsScheduled(root,eventTime);schedulePendingInteractions(根,局域网e);}mostRecentlyUpdatedRoot=root;}我们可以看到关键代码if(executionContext===NoContext){resetRenderTimer();flushSyncCallbackQueue();}flushSyncCallbackQueue的作用是同步执行更新队列中的更新。之前我们说过,setState之后,更新对象update会以队列的形式保存。这里是执行这些更新并清除队列。我们可以看到,如果当前的执行上下文executionContext是NoContext,说明React已经不在自己的调度环节结束了,但是当无事可做的时候,React会同步执行setState的回调函数来更新。那么,什么时候会是无所事事的状态呢?答案是当不在React自身的调度阶段时,比如setTimeout、网络请求、直接绑定在Dom节点上的事件等,这些行为都不会触发React的调度行为。当React在自己的调度阶段时,会根据自己所处的状态,给executionContext赋值不同的值。比如当给BatchedContext赋值时,就意味着进入了update和merge阶段,executionContext默认为NoContext。所以当不在调度阶段时,React会进入一个无事可做的状态,会同步执行setState。当React在自己的调度阶段,会执行生命周期钩子函数、合成事件等函数,这些情况下会触发batchedUpdate合并更新,所以此时给BatchedContext赋值executionContext自然是一个异步行为。在我们的示例中,componentDidMount是React调度过程的一部分,因此setState将异步执行。componentDidMount执行完毕后,React会退出调度过程。这个时候executionContext是NoContext,但是我们的代码还没有执行完。完了,因为setTimeout是一个异步方法,当它的回调执行时,React会同步执行setState。setState和useStateuseState是React给函数组件赋予状态的一种方式,但是和setState的调用栈有重叠,即会进入scheduleUpdateOnFiber函数,所以在同步和异步行为上没有区别。
