React源碼解析之scheduleWork(下)
- 2019 年 10 月 6 日
- 筆記
八、scheduleCallbackForRoot() 作用: 在render()
之後,立即執行調度任務
源碼:
// Use this function, along with runRootCallback, to ensure that only a single // callback per root is scheduled. It's still possible to call renderRoot // directly, but scheduling via this function helps avoid excessive callbacks. // It works by storing the callback node and expiration time on the root. When a // new callback comes in, it compares the expiration time to determine if it // should cancel the previous one. It also relies on commitRoot scheduling a // callback to render the next level, because that means we don't need a // separate callback per expiration time. //同步調用callback //流程是在root上存取callback和expirationTime, // 當新的callback調用時,比較更新expirationTime function scheduleCallbackForRoot( root: FiberRoot, priorityLevel: ReactPriorityLevel, expirationTime: ExpirationTime, ) { //獲取root的回調過期時間 const existingCallbackExpirationTime = root.callbackExpirationTime; //更新root的回調過期時間 if (existingCallbackExpirationTime < expirationTime) { // New callback has higher priority than the existing one. //當新的expirationTime比已存在的callback的expirationTime優先順序更高的時候 const existingCallbackNode = root.callbackNode; if (existingCallbackNode !== null) { //取消已存在的callback(打斷) //將已存在的callback節點從鏈表中移除 cancelCallback(existingCallbackNode); } //更新callbackExpirationTime root.callbackExpirationTime = expirationTime; //如果是同步任務 if (expirationTime === Sync) { // Sync React callbacks are scheduled on a special internal queue //在臨時隊列中同步被調度的callback root.callbackNode = scheduleSyncCallback( runRootCallback.bind( null, root, renderRoot.bind(null, root, expirationTime), ), ); } else { let options = null; if (expirationTime !== Never) { //(Sync-2 - expirationTime) * 10-now() let timeout = expirationTimeToMs(expirationTime) - now(); options = {timeout}; } //callbackNode即經過處理包裝的新task root.callbackNode = scheduleCallback( priorityLevel, //bind()的意思是綁定this,xx.bind(y)()這樣才算執行 runRootCallback.bind( null, root, renderRoot.bind(null, root, expirationTime), ), options, ); if ( enableUserTimingAPI && expirationTime !== Sync && (executionContext & (RenderContext | CommitContext)) === NoContext ) { // Scheduled an async callback, and we're not already working. Add an // entry to the flamegraph that shows we're waiting for a callback // to fire. //開始調度callback的標誌 startRequestCallbackTimer(); } } } // Associate the current interactions with this new root+priority. //跟蹤這些update,並計數、檢測它們是否會報錯 schedulePendingInteractions(root, expirationTime); }
解析: Fiber機制可以為每一個update任務進行優先順序排序,並且可以記錄調度到了哪裡(schedulePendingInteractions())
同時,還可以中斷正在執行的任務,優先執行優先順序比當前高的任務(scheduleCallbackForRoot()
),之後,還可以繼續之前中斷的任務,而React16 之前調用setState()
,必須等待setState
的update
隊列全部調度完,才能進行之後的操作。
一起看下scheduleCallbackForRoot()
做了什麼: (1)當新的scheduleCallback
的優先順序更高時,中斷當前任務cancelCallback(existingCallbackNode)
(2)如果是同步任務,則在臨時隊列中進行調度 (3)如果是非同步任務,則更新調度隊列的狀態 (4)設置開始調度的時間節點 (5)跟蹤調度的任務
具體講解,請耐心往下看
九、cancelCallback() 作用: 中斷正在執行的調度任務
源碼:
const { unstable_cancelCallback: Scheduler_cancelCallback, } = Scheduler; //從鏈表中移除task節點 function unstable_cancelCallback(task) { //獲取callbackNode的next節點 var next = task.next; //由於鏈表是雙向循環鏈表,一旦next是null則證明該節點已不存在於鏈表中 if (next === null) { // Already cancelled. return; } //自己等於自己,說明鏈表中就這一個callback節點 //firstTask/firstDelayedTask應該是類似游標的概念,即正要執行的節點 if (task === next) { //置為null,即刪除callback節點 //重置firstTask/firstDelayedTask if (task === firstTask) { firstTask = null; } else if (task === firstDelayedTask) { firstDelayedTask = null; } } else { //將firstTask/firstDelayedTask指向下一節點 if (task === firstTask) { firstTask = next; } else if (task === firstDelayedTask) { firstDelayedTask = next; } var previous = task.previous; //熟悉的鏈表操作,刪除已存在的callbackNode previous.next = next; next.previous = previous; } task.next = task.previous = null; }
解析: 操作schedule
鏈表,將正要執行的callback
「移除」,將游標指向下一個調度任務
十、scheduleSyncCallback() 作用: 如果是同步任務的話,則執行scheduleSyncCallback()
,將調度任務入隊,並返回入隊後的臨時隊列
源碼:
//入隊callback,並返回臨時的隊列 export function scheduleSyncCallback(callback: SchedulerCallback) { // Push this callback into an internal queue. We'll flush these either in // the next tick, or earlier if something calls `flushSyncCallbackQueue`. //在下次調度或調用 刷新同步回調隊列 的時候刷新callback隊列 //如果同步隊列為空的話,則初始化同步隊列, //並在下次調度的一開始就刷新隊列 if (syncQueue === null) { syncQueue = [callback]; // Flush the queue in the next tick, at the earliest. immediateQueueCallbackNode = Scheduler_scheduleCallback( //賦予調度立即執行的高許可權 Scheduler_ImmediatePriority, flushSyncCallbackQueueImpl, ); } //如果同步隊列不為空的話,則將callback入隊 else { // Push onto existing queue. Don't need to schedule a callback because // we already scheduled one when we created the queue. //在入隊的時候,不必去調度callback,因為在創建隊列的時候就已經調度了 syncQueue.push(callback); } //fake我認為是臨時隊列的意思 return fakeCallbackNode; }
解析: (1)當同步隊列為空 調用Scheduler_scheduleCallback()
,將該callback
任務入隊,並把該callback
包裝成newTask
,賦給root.callbackNode
Scheduler_scheduleCallback():
const { unstable_scheduleCallback: Scheduler_scheduleCallback, } = Scheduler; //返回經過包裝處理的task function unstable_scheduleCallback(priorityLevel, callback, options) { var currentTime = getCurrentTime(); var startTime; var timeout; //更新startTime(默認是現在)和timeout(默認5s) if (typeof options === 'object' && options !== null) { var delay = options.delay; if (typeof delay === 'number' && delay > 0) { startTime = currentTime + delay; } else { startTime = currentTime; } timeout = typeof options.timeout === 'number' ? options.timeout : timeoutForPriorityLevel(priorityLevel); } else { // Times out immediately // var IMMEDIATE_PRIORITY_TIMEOUT = -1; // Eventually times out // var USER_BLOCKING_PRIORITY = 250; //普通優先順序的過期時間是5s // var NORMAL_PRIORITY_TIMEOUT = 5000; //低優先順序的過期時間是10s // var LOW_PRIORITY_TIMEOUT = 10000; timeout = timeoutForPriorityLevel(priorityLevel); startTime = currentTime; } //過期時間是當前時間+5s,也就是默認是5s後,react進行更新 var expirationTime = startTime + timeout; //封裝成新的任務 var newTask = { callback, priorityLevel, startTime, expirationTime, next: null, previous: null, }; //如果開始調度的時間已經錯過了 if (startTime > currentTime) { // This is a delayed task. //將延期的callback插入到延期隊列中 insertDelayedTask(newTask, startTime); //如果調度隊列的頭任務沒有,並且延遲調度隊列的頭任務正好是新任務, //說明所有任務均延期,並且此時的任務是第一個延期任務 if (firstTask === null && firstDelayedTask === newTask) { // All tasks are delayed, and this is the task with the earliest delay. //如果延遲調度開始的flag為true,則取消定時的時間 if (isHostTimeoutScheduled) { // Cancel an existing timeout. cancelHostTimeout(); } //否則設為true else { isHostTimeoutScheduled = true; } // Schedule a timeout. requestHostTimeout(handleTimeout, startTime - currentTime); } } //沒有延期的話,則按計劃插入task else { insertScheduledTask(newTask, expirationTime); // Schedule a host callback, if needed. If we're already performing work, // wait until the next time we yield. //更新調度執行的標誌 if (!isHostCallbackScheduled && !isPerformingWork) { isHostCallbackScheduled = true; requestHostCallback(flushWork); } } //返回經過包裝處理的task return newTask; }
當有新的update時,React 默認是 5s 後進行更新(直觀地說,就是你更新了開發程式碼,過 5s,也可以說是最遲過了 5s,網頁更新)
Scheduler_scheduleCallback()
的作用是: ① 確定當前時間startTime
和延遲更新時間timeout
② 新建newTask
對象(包含callback
、expirationTime
) ③ 如果是延遲調度的話,將newTask
放入【延遲調度隊列】 ④ 如果是正常調度的話,將newTask
放入【正常調度隊列】 ⑤ 返回包裝的newTask
(2)當同步隊列不為空 將該callback
入隊
scheduleSyncCallback()
最終返回臨時回調節點。
十一、scheduleCallback() 作用: 如果是非同步任務的話,則執行scheduleCallback()
,對callback
進行包裝處理,並更新調度隊列的狀態
源碼:
//對callback進行包裝處理,並更新調度隊列的狀態 export function scheduleCallback( reactPriorityLevel: ReactPriorityLevel, callback: SchedulerCallback, options: SchedulerCallbackOptions | void | null, ) { //獲取調度的優先順序 const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel); return Scheduler_scheduleCallback(priorityLevel, callback, options); }
解析: 和十
一樣,調用Scheduler_scheduleCallback()
,將該callback
任務入隊,並把該callback
包裝成newTask
,賦給root.callbackNode
tips: func.bind(xx)
的意思是func
里的this
綁定的是xx
, 也就是說 是xx
調用func
方法
注意!func.bind(xx)
這僅僅是綁定,而不是調用!
func.bind(xx)()
這樣才算是xx
調用func
方法!
至此,scheduleCallbackForRoot()
已分析完畢(八到十一)
我們講到這裡了:
export function scheduleUpdateOnFiber(){ xxx xxx xxx if (expirationTime === Sync) { if( 第一次render ){ xxx }else{ /*八到十一講解的內容*/ scheduleCallbackForRoot(root, ImmediatePriority, Sync); //當前沒有update時 if (executionContext === NoContext) { //刷新同步任務隊列 flushSyncCallbackQueue(); } } } }
十二、flushSyncCallbackQueue() 作用: 更新同步任務隊列的狀態
源碼:
//刷新同步任務隊列 export function flushSyncCallbackQueue() { //如果即時節點存在則中斷當前節點任務,從鏈表中移除task節點 if (immediateQueueCallbackNode !== null) { Scheduler_cancelCallback(immediateQueueCallbackNode); } //更新同步隊列 flushSyncCallbackQueueImpl(); }
flushSyncCallbackQueueImpl():
//更新同步隊列 function flushSyncCallbackQueueImpl() { //如果同步隊列未更新過並且同步隊列不為空 if (!isFlushingSyncQueue && syncQueue !== null) { // Prevent re-entrancy. //防止重複執行,相當於一把鎖 isFlushingSyncQueue = true; let i = 0; try { const isSync = true; const queue = syncQueue; //遍歷同步隊列,並更新刷新的狀態isSync=true runWithPriority(ImmediatePriority, () => { for (; i < queue.length; i++) { let callback = queue[i]; do { callback = callback(isSync); } while (callback !== null); } }); //遍歷結束後置為null syncQueue = null; } catch (error) { // If something throws, leave the remaining callbacks on the queue. if (syncQueue !== null) { syncQueue = syncQueue.slice(i + 1); } // Resume flushing in the next tick Scheduler_scheduleCallback( Scheduler_ImmediatePriority, flushSyncCallbackQueue, ); throw error; } finally { isFlushingSyncQueue = false; } } }
解析: 當前調度的任務被中斷時,先從鏈表中「移除」當前節點,並調用flushSyncCallbackQueueImpl ()
任務更新同步隊列
循環遍歷syncQueue
,並更新節點的isSync
狀態(isSync=true)
然後到這裡:
export function scheduleUpdateOnFiber(){ xxx xxx xxx if (expirationTime === Sync) { if( 第一次render ){ xxx }else{ /*八到十一講解的內容*/ scheduleCallbackForRoot(root, ImmediatePriority, Sync); if (executionContext === NoContext) { //十二講的內容 flushSyncCallbackQueue(); } } } //如果是非同步任務的話,則立即執行調度任務 //對應if (expirationTime === Sync) else { scheduleCallbackForRoot(root, priorityLevel, expirationTime); } if ( (executionContext & DiscreteEventContext) !== NoContext && // Only updates at user-blocking priority or greater are considered // discrete, even inside a discrete event. // 只有在用戶阻止優先順序或更高優先順序的更新才被視為離散,即使在離散事件中也是如此 (priorityLevel === UserBlockingPriority || priorityLevel === ImmediatePriority) ) { // This is the result of a discrete event. Track the lowest priority // discrete update per root so we can flush them early, if needed. //這是離散事件的結果。 跟蹤每個根的最低優先順序離散更新,以便我們可以在需要時儘早清除它們。 //如果rootsWithPendingDiscreteUpdates為null,則初始化它 if (rootsWithPendingDiscreteUpdates === null) { //key是root,value是expirationTime rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]); } else { //獲取最新的DiscreteTime const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root); //更新DiscreteTime if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) { rootsWithPendingDiscreteUpdates.set(root, expirationTime); } } } }
scheduleUpdateOnFiber()的最後這一段沒看懂是什麼意思,猜測是調度結束之前,更新離散時間。
十三、scheduleWork流程圖

GitHub: https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberWorkLoop.js
覺得不錯的話,在下方留言都是一種支援 (●'◡'●)ノ 。
(完)