React源碼解析之scheduleWork(下)

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

8、scheduleCallbackForRoot()
做用:
render()以後,當即執行調度任務java

源碼:node

// 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())react

同時,還能夠中斷正在執行的任務,優先執行優先級比當前高的任務(scheduleCallbackForRoot()),以後,還能夠繼續以前中斷的任務,而React16 以前調用setState(),必須等待setStateupdate隊列所有調度完,才能進行以後的操做。git

一塊兒看下scheduleCallbackForRoot()作了什麼:
(1)當新的scheduleCallback的優先級更高時,中斷當前任務cancelCallback(existingCallbackNode)
(2)若是是同步任務,則在臨時隊列中進行調度
(3)若是是異步任務,則更新調度隊列的狀態
(4)設置開始調度的時間節點
(5)跟蹤調度的任務github

具體講解,請耐心往下看web

9、cancelCallback()
做用:
中斷正在執行的調度任務app

源碼:異步

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「移除」,將遊標指向下一個調度任務async

10、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,
    nextnull,
    previousnull,
  };
  //若是開始調度的時間已經錯過了
  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()最終返回臨時回調節點。


11、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();
      }
    }  
  }
}
複製代碼

12、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()的最後這一段沒看懂是什麼意思,猜想是調度結束以前,更新離散時間。


十3、scheduleWork流程圖

GitHub:
github.com/AttackXiaoJ…


(完)

相關文章
相關標籤/搜索