React源碼解析之scheduleWork(下)

NO IMAGE
React源碼解析之scheduleWork(下)

上篇回顧:
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流程圖

React源碼解析之scheduleWork(下)

GitHub:
github.com/AttackXiaoJ…


React源碼解析之scheduleWork(下)

(完)

相關文章

(iOS)UICollectionViewLayoutInvalidationContext性能優化詳細流程圖+範例

(iOS)向Hero致敬與分析(一)Double研究所

React之childExpirationTime

React源碼解析之flushWork