You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functionbatchedUpdates(fn,a){varprevExecutionContext=executionContext;// 保存原来的值executionContext|=EventContext;try{returnfn(a);// 调用我们的合成事件逻辑onBtnClick}finally{executionContext=prevExecutionContext;// 函数执行完成恢复成原来的值if(executionContext===NoContext){// Flush the immediate callbacks that were scheduled during this batchresetRenderTimer();flushSyncCallbackQueue();}}}constbatchedEventUpdates=batchedUpdates;
回到 dispatchAction 方法中,这个方法主要是构造更新队列,然后调用 scheduleUpdateOnFiber 开始调度更新,异步 or 同步更新的逻辑主要在这个函数的流程中!!scheduleUpdateOnFiber 主要流程如下:
constSyncLane=1;constSyncLanePriority=15;constNoContext=0;letexecutionContext=NoContext;letsyncQueue=[];constscheduleUpdateOnFiber=(fiber,lane,eventTime)=>{constroot=markUpdateLaneFromFiberToRoot(fiber);if(lane===SyncLane){// 开始创建一个任务,从根节点开始进行更新ensureRootIsScheduled(root);// 如果当前的executionContext执行上下文环境是NoContext(非批量)if(executionContext===NoContext){// 需要注意,我们在ensureRootIsScheduled函数中,将flushSyncCallbackQueue放在了微任务中去执行,// 但是如果executionContext是同步更新的话,这里会直接调用flushSyncCallbackQueue开始更新任务,更新完成后// flushSyncCallbackQueue会清空syncQueueflushSyncCallbackQueue();}}};functionensureRootIsScheduled(root){constnewCallbackPriority=returnNextLanesPriority();constexistingCallbackPriority=root.callbackPriority;if(existingCallbackPriority===newCallbackPriority){// The priority hasn't changed. We can reuse the)return;}if(newCallbackPriority===SyncLanePriority){newCallbackNode=scheduleSyncCallback(performSyncWorkOnRoot.bind(null,root));}root.callbackPriority=newCallbackPriority;}// 其实就是把performSyncWorkOnRoot函数添加到队列里,在下一个微任务里面执行functionscheduleSyncCallback(callback){if(syncQueue===null){syncQueue=[callback];// Flush the queue in the next tick, at the earliest.immediateQueueCallbackNode=Scheduler_scheduleCallback(Scheduler_ImmediatePriority,flushSyncCallbackQueue);}else{syncQueue.push(callback);}}// flushSyncCallbackQueue简单实现如下:functionflushSyncCallbackQueue(){syncQueue.forEach((cb)=>cb());syncQueue=null;}
批处理(异步更新)机制简述
在
React
源码中,通过全局变量executionContext
控制React
执行上下文,指示React
开启同步或者异步更新。executionContext
一开始被初始化为NoContext
,因此React
默认是同步更新的。当我们在合成事件中调用
setState
时:实际上合成事件会调用
batchedEventUpdates(onBtnClick)
,将我们的函数onBtnClick
拦截一层。batchedEventUpdates
实现如下:可以看出该方法在执行时会更改
executionContext
指示React
异步更新。这也是为什么我们在合成事件中多次调用setState
,而React
只会更新一次的原因。函数执行完成,executionContext
又会恢复成原来的值。如果我们的setState
逻辑是在setTimeout
中,当合成事件执行完毕,此时executionContext
恢复成原来的值,setTimeout
中的setState
就变成了同步更新在
React17
版本中提供了一个unstable_batchedUpdates
API,如果我们希望在setTimeout
等异步任务中开启批量更新,则可以使用这个方法包裹一下我们的业务代码。更新队列 syncQueue
React
使用syncQueue
维护一个更新队列。syncQueue
数组存的是performSyncWorkOnRoot
,performSyncWorkOnRoot
这个方法从根节点开始更新在
scheduleSyncCallback
函数中如果syncQueue
为null
,则初始化一个数组,开启一个微任务调度。而如果syncQueue
不为null
,则添加进更新队列,此时不需要再重新开启一个微任务调度如果
executionContext === NoContext
则直接刷新syncQueue
批量更新场景
在合成事件等
React
能够接管的场景中,setState
是批量更新的。点击按钮,查看控制台可以发现只打印了一次:render====== 2
同步更新场景
在
setTimeout
、Promise回调
等异步任务
场景中,setState
是同步更新的。点击按钮,查看控制台可以发现打印了两句话:render====== 1
render====== 2
批量更新机制主流程源码
在
onClick
函数里加一行debugger
。点击按钮,开始 debug。首先执行的是dispatchAction
函数,但是如果我们追溯函数调用栈,可以发现实际上是会先执行合成事件相关的函数:合成事件调用了
batchedEventUpdates
,此时executionContext
已经被设置为批量更新了回到
dispatchAction
方法中,这个方法主要是构造更新队列,然后调用scheduleUpdateOnFiber
开始调度更新,异步 or 同步更新的逻辑主要在这个函数的流程中!!scheduleUpdateOnFiber
主要流程如下:performSyncWorkOnRoot
从根节点开始更新,这个不属于本节内容。当我们点击按钮,从合成事件派发到
React
从当前fiber
节点开始调度更新,并且决定是异步或者同步更新的主要流程如下图:The text was updated successfully, but these errors were encountered: