React源碼解析之scheduleWork(下)

  • 2019 年 10 月 6 日
  • 筆記

上篇回顧: React源碼解析之scheduleWork(上)

八、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(),必須等待setStateupdate隊列全部調度完,才能進行之後的操作。

一起看下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對象(包含callbackexpirationTime) ③ 如果是延遲調度的話,將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


覺得不錯的話,在下方留言都是一種支援 (●'◡'●)ノ

(完)