React源碼解析之flushWork

前言:
先看一下flushWorkReact源碼解析之requestHostCallback 中哪裏用到了:javascript

xxx
requestHostCallback(flushWork)
xxx
xxx
requestHostCallback = function(callback{
  scheduledHostCallback = callback;

}
...
...
channel.port1.onmessage = function(event{
  xxx
  xxx
  const hasMoreWork = scheduledHostCallback(
          hasTimeRemaining,
          currentTime,
        );
  xxx
  xxx
}
複製代碼

flushWork=>callback=>scheduledHostCallback=>hasMoreWork
對,就是這個hasMoreWork調用了flushWork,本文就講解下flushWork()方法。php

1、flushWork
做用:
刷新調度隊列,執行調度任務java

源碼:react

//const hasTimeRemaining = frameDeadline - currentTime > 0
//hasTimeRemaining 是每一幀內留給 react 的時間
//initialTime 即 currentTime
function flushWork(hasTimeRemaining, initialTime{
  // Exit right away if we're currently paused
  //若是 React 沒有掌握瀏覽器的控制權,則不執行調度任務
  if (enableSchedulerDebugging && isSchedulerPaused) {
    return;
  }

  // We'll need a host callback the next time work is scheduled.
  //調度任務執行的標識
  //調度任務是否執行
  isHostCallbackScheduled = false;
  //調度任務是否超時
  //一旦超時,
  if (isHostTimeoutScheduled) {
    // We scheduled a timeout but it's no longer needed. Cancel it.
    isHostTimeoutScheduled = false;
    /*cancelHostCallback*/
    cancelHostTimeout();
  }

  let currentTime = initialTime;
  // 檢查是否有不過時的任務,並把它們加入到新的調度隊列中
  advanceTimers(currentTime);
  /*isExecutingCallback 是否正在調用callback*/
  isPerformingWork = true;
  try {
    // 若是在一幀內執行時間超時,沒有時間讓 React 執行調度任務的話
    if (!hasTimeRemaining) {
      // Flush all the expired callbacks without yielding.
      // TODO: Split flushWork into two separate functions instead of using
      // a boolean argument?
      //一直執行過時的任務,直到到達一個不過時的任務爲止
      while (
        /*firstTask即firstCallbackNode*/
        firstTask !== null &&
        //若是firstTask.expirationTime一直小於等於currentTime的話,則一直執行flushTask方法
        firstTask.expirationTime <= currentTime &&
        !(enableSchedulerDebugging && isSchedulerPaused)
      ) {
        /*flushTask即flushFirstCallback*/
        flushTask(firstTask, currentTime);
        currentTime = getCurrentTime();
        //檢查是否有不過時的任務,並把它們加入到新的調度隊列中
        advanceTimers(currentTime);
      }
    } else {
      // Keep flushing callbacks until we run out of time in the frame.
      //除非在一幀內執行時間超時,不然一直刷新 callback 隊列
      //仍有時間剩餘而且舊調度隊列不爲空時,將不過時的任務加入到新的調度隊列中
      if (firstTask !== null) {
        do {
          flushTask(firstTask, currentTime);
          currentTime = getCurrentTime();
          advanceTimers(currentTime);
        } while (
          firstTask !== null &&
          !shouldYieldToHost() &&
          !(enableSchedulerDebugging && isSchedulerPaused)
        );
      }
    }
    // Return whether there's additional work
    if (firstTask !== null) {
      return true;
    } else {
      if (firstDelayedTask !== null) {
        // 執行延期的任務
        requestHostTimeout(
          handleTimeout,
          firstDelayedTask.startTime - currentTime,
        );
      }
      return false;
    }
  } finally {
    isPerformingWork = false;
  }
}
複製代碼

解析:
(1) 判斷 React 是否掌握瀏覽器的控制權,若是沒有,則不執行調度任務git

(2) 若是調度任務超時,則調用cancelHostTimeout(),取消執行調度任務github

(3) 調用advanceTimers(),檢查是否有不過時的任務,並把它們加入到新的調度隊列中web

(4) 若是能執行到此步,意味着能夠執行調度任務,設isPerformingWorktrue瀏覽器

(5) 若是 React 的執行時間沒有剩餘,可是調度隊列存在,且調度任務過時時
① 調用flushTask(),將調度任務從調度隊列中拿出並執行,以後將調度任務生出的子調度任務插入到其後
② 調用getCurrentTime(),刷新當前時間
③ 調用advanceTimers(),檢查是否有不過時的任務,並把它們加入到新的調度隊列中安全

(6) 若是 React 的執行時間有剩餘,可是調度隊列存在,且調度任務未被中斷時
① 調用flushTask(),將調度任務從調度隊列中拿出並執行,執行調度任務生出的子調度任務
② 調用getCurrentTime(),刷新當前時間
③ 調用advanceTimers(),檢查是否有不過時的任務,並把它們加入到新的調度隊列中app

(7) 若是調度任務都執行完畢,則返回 true,不然返回 false,執行延期的任務

2、cancelHostTimeout
做用:
取消執行調度任務

源碼:

  cancelHostTimeout = function({
    localClearTimeout(timeoutID);
    timeoutID = -1;
  };
複製代碼

解析:
React源碼解析之requestHostCallback 中有涉及到,就是取消 B 執行調度任務

3、advanceTimers
做用:
檢查是否有不過時的任務,並把它們加入到新的調度隊列中

源碼:

//檢查是否有不過時的任務,並把它們加入到新的調度隊列中
function advanceTimers(currentTime{
  // Check for tasks that are no longer delayed and add them to the queue.
  //開始時間已經晚於當前時間了
  if (firstDelayedTask !== null && firstDelayedTask.startTime <= currentTime) {
    do {
      const task = firstDelayedTask;
      const next = task.next;
      //調度任務隊列是一個環狀的鏈表
      //說明只有一個過時任務,將其置爲 null
      if (task === next) {
        firstDelayedTask = null;
      }
      //將當前的 task 擠掉
      else {
        firstDelayedTask = next;
        const previous = task.previous;
        previous.next = next;
        next.previous = previous;
      }
      //讓 task 擺脫與舊的調度隊列的依賴
      task.next = task.previous = null;
      //將 task 插入到新的調度隊列中
      insertScheduledTask(task, task.expirationTime);
    } while (
      firstDelayedTask !== null &&
      firstDelayedTask.startTime <= currentTime
    );
  }
}
複製代碼

解析:
(1) firstDelayedTask 表示過時的任務
(2) 若是 過時任務存在,而且仍未執行,則將該 task 拿出
(3) 調用insertScheduledTask將 task 插入到新的調度隊列中

4、insertScheduledTask
做用:
將 newTask 插入到新的調度隊列中

源碼:

//將 newTask 插入到新的調度隊列中
function insertScheduledTask(newTask, expirationTime) {
  // Insert the new task into the list, ordered first by its timeout, then by
  // insertion. So the new task is inserted after any other task the
  // same timeout
  if (firstTask === null) {
    // This is the first task in the list.
    firstTask = newTask.next = newTask.previous = newTask;
  } else {
    var next = null;
    var task = firstTask;
    //React對傳進來的 callback 進行排序,
    // 優先級高的排在前面,優先級低的排在後面
    do {
      if (expirationTime < task.expirationTime) {
        // The new task times out before this one.
        next = task;
        break;
      }
      task = task.next;
    } while (task !== firstTask);
    //優先級最小的話
    if (next === null) {
      // No task with a later timeout was found, which means the new task has
      // the latest timeout in the list.
      next = firstTask;
    }
    //優先級最高的話
    else if (next === firstTask) {
      // The new task has the earliest expiration in the entire list.
      firstTask = newTask;
    }
    //插入 newTask
    var previous = next.previous;
    previous.next = next.previous = newTask;
    newTask.next = next;
    newTask.previous = previous;
  }
}
複製代碼

解析:
這段比較簡單,主要是鏈表的一些操做,邏輯就是:
按照傳進來的 task 的優先級高低排序,並插入到新的調度隊列中

5、flushTask
做用:
將調度任務從調度隊列中拿出,並執行,以後將調度任務生出的子調度任務插入到其後

源碼:

// 將調度任務從調度隊列中拿出,並執行;
// 將調度任務生出的子調度任務插入到其後
function flushTask(task, currentTime) {
  // Remove the task from the list before calling the callback. That way the
  // list is in a consistent state even if the callback throws.
  // 將過時的任務在調度前從調度隊列中移除,以讓調度隊列的任務均保持不過時(一致)的狀態
  const next = task.next;
  // 若是當前隊列中只有一個回調任務,則清空隊列
  if (next === task) {
    // This is the only scheduled task. Clear the list.
    firstTask = null;
  }
  else {
    // Remove the task from its position in the list.
    //若是當前任務正好等於firstTask,則firstTask指向下一個回調任務
    if (task === firstTask) {
      firstTask = next;
    }
    // 將該 task 從調度隊列中拿出來
    const previous = task.previous;
    previous.next = next;
    next.previous = previous;
  }
  // 單獨拿出 task,以便安全地執行它
  task.next = task.previous = null;

  // Now it's safe to execute the task.
  var callback = task.callback;
  // 以前的調度優先級
  var previousPriorityLevel = currentPriorityLevel;
  // 以前的調度任務
  var previousTask = currentTask;

  // 當前任務
  currentPriorityLevel = task.priorityLevel;
  currentTask = task;
  // 回調任務返回的內容
  var continuationCallback;
  try {
    // 當前的回調任務是否超時,false 超時,true 沒有
    var didUserCallbackTimeout = task.expirationTime <= currentTime;
    // 執行回調任務,返回的結果由 continuationCallback 保存
    continuationCallback = callback(didUserCallbackTimeout);
  } catch (error) {
    throw error;
  } finally {
    // 重置任務優先級和任務
    currentPriorityLevel = previousPriorityLevel;
    currentTask = previousTask;
  }

  // A callback may return a continuation. The continuation should be scheduled
  // with the same priority and expiration as the just-finished callback.
  // 調度任務可能會有返回的內容,若是返回的是一個 function,
  // 該 function 應該和剛剛執行的 callback 同樣,有一樣的優先級
  if (typeof continuationCallback === 'function') {
    var expirationTime = task.expirationTime;
    // 將回調任務的結果再拼成一個子回調任務
    var continuationTask = {
      callback: continuationCallback,
      priorityLevel: task.priorityLevel,
      startTime: task.startTime,
      expirationTime,
      next: null,
      previous: null,
    };

    // Insert the new callback into the list, sorted by its timeout. This is
    // almost the same as the code in `scheduleCallback`, except the callback
    // is inserted into the list *before* callbacks of equal timeout instead
    // of after.

    // 若是調度隊列爲空的話,將子回調任務插入調度隊列
    if (firstTask === null) {
      // This is the first callback in the list.
      firstTask = continuationTask.next = continuationTask.previous = continuationTask;
    }
    //判斷子回調任務的優先級
    else {
      var nextAfterContinuation = null;
      var t = firstTask;
      // 若是當前調度優先級小於 firstTask 的優先級的話,
      // 下一個要執行的調度任務就是 firstTask
      // ps:可是這個循環感受不會執行,由於 var t = firstTask;
      do {

        if (expirationTime <= t.expirationTime) {
          // This task times out at or after the continuation. We will insert
          // the continuation *before* this task.
          nextAfterContinuation = t;
          break;
        }
        t = t.next;
      } while (t !== firstTask);
      if (nextAfterContinuation === null) {
        // No equal or lower priority task was found, which means the new task
        // is the lowest priority task in the list.
        //沒有相同或更低的優先級的調度任務找到,意味着新任務就是最低優先級的任務
        nextAfterContinuation = firstTask;
      }
      //不然新任務是最高優先級的任務
      else if (nextAfterContinuation === firstTask) {
        // The new task is the highest priority task in the list.
        firstTask = continuationTask;
      }
      // 將子回調任務插入調度隊列中
      const previous = nextAfterContinuation.previous;
      previous.next = nextAfterContinuation.previous = continuationTask;
      continuationTask.next = nextAfterContinuation;
      continuationTask.previous = previous;
    }
  }
}
複製代碼

解析:
(1) 將 task 從調度隊列中拿出
(2) 執行該 task,返回的結果由continuationCallback保存
(3) 若是continuationCallback返回的是一個function,將該回調任務的結果再拼成一個子回調任務
(4) 將子回調任務插入調度隊列中

6、總結
本文的源碼邏輯不算複雜,可是須要熟悉鏈表的操做。

GitHub:
github.com/AttackXiaoJ…


(完)

相關文章
相關標籤/搜索