React Fiber源碼分析 第二篇(同步模式)

系列文章

React Fiber源碼分析 第一篇
React Fiber源碼分析 第二篇(同步模式)

前言

React Fiber是React在V16版本中的大更新,利用了閒餘時間看了一些源碼,做個小記錄~
如果有錯誤,請輕噴

流程圖

流程圖1

image

流程圖2

image

源碼分析

1.scheduleRootUpdate這個函數主要執行了兩個操作 1個是創建更新createUpdate並放到更新隊列enqueueUpdate, 1個是執行sheculeWork函數

function scheduleRootUpdate(current$$1, element, expirationTime, callback) {
var update = createUpdate(expirationTime);
  update.payload = { element: element };

  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    update.callback = callback;
  }
  enqueueUpdate(current$$1, update);

  scheduleWork(current$$1, expirationTime);
  return expirationTime;
}

2.先從createUpdate函數分析, 他直接返回了一個包含了更新信息的對象

function createUpdate(expirationTime) {
  return {
    // 優先級
    expirationTime: expirationTime,
    // 更新類型
    tag: UpdateState,
    // 更新的對象
    payload: null,
    callback: null,
    // 指向下一個更新
    next: null,
    // 指向下一個更新effect
    nextEffect: null
  };
}

3.接着更新payload和callback屬性, payload即爲更新的對象, 然後執行enqueuUpdateenqueueUpdate相對比較容易理解, 不過裏面有一註釋挺重要

Both queues are non-empty. The last update is the same in both lists, because of structural sharing. So, only append to one of the lists 意思是alternate的updateQueue和fiber的updateQueue是同一個對象引用,這裏會在createWorkInProcess提到

往下走就是重要的scheduleWork, 它是render階段真正的開始

function scheduleWork(fiber, expirationTime) {
  // 更新優先級
  var root = scheduleWorkToRoot(fiber, expirationTime);
  ...if (!isWorking && nextRenderExpirationTime !== NoWork && expirationTime < nextRenderExpirationTime) {
    // This is an interruption. (Used for performance tracking.) 如果這是一個打斷原有更新的任務, 先把現有任務記錄
    interruptedBy = fiber;
    resetStack();
  }
  // 設置下一個操作時間nextExpirationTimeToWorkOn
  markPendingPriorityLevel(root, expirationTime);
  if (
  // If we're in the render phase, we don't need to schedule this root
  // for an update, because we'll do it before we exit...
  !isWorking || isCommitting$1 ||
  // ...unless this is a different root than the one we're rendering.
  nextRoot !== root) {
    var rootExpirationTime = root.expirationTime;
    requestWork(root, rootExpirationTime);
  }
  ...
}

4.scheduleWork先執行一個scheduleWorkToRoot函數, 該函數主要是更新其expirationTime以及上層fiberchildrenExpirationTime

function scheduleWorkToRoot(fiber, expirationTime) {
  // Update the source fiber's expiration time
  if (fiber.expirationTime === NoWork || fiber.expirationTime > expirationTime) {
    fiber.expirationTime = expirationTime;
  }
  var alternate = fiber.alternate;
  if (alternate !== null && (alternate.expirationTime === NoWork || alternate.expirationTime > expirationTime)) {
    alternate.expirationTime = expirationTime;
  }
  // 如果是HostRoot 即直接返回
  var node = fiber.return;
  if (node === null && fiber.tag === HostRoot) {
    return fiber.stateNode;
  }
  // 若子fiber中有更新, 即更新其childrenExpirationTime
  while (node !== null) {
    ...
  }
  return null;
}

5.接着會執行一個markPendingPriorityLevel函數,這個函數主要是更新root的最高優先級和最低優先級(earliestPendingTime和lastestPendingTime;), 同時設置下一個執行操作的時間nextExpirationTimeToWorkOn(即root中具有最高優先級的fiber的expirationTime),關於這個函數的latestSuspendedTime;以後再說

最後scheduleWork會執行requestWork

function requestWork(root, expirationTime) {
  addRootToSchedule(root, expirationTime);
  if (isRendering) {
    // rendering狀態,直接返回
    return;
  }

  if (isBatchingUpdates) {
    // isBatchingUpdates, 直接返回。 react的state更新是會合並的
    ...return;
  }

  // TODO: Get rid of Sync and use current time?
  if (expirationTime === Sync) {
    // 執行同步
    performSyncWork();
  } else {
    // 異步, 暫不分析
    scheduleCallbackWithExpirationTime(root, expirationTime);
  }
}

6.requestWork 會先執行addRootToSchedule,由函數名稱可知其作用,將root加到schedule, 即設置firstScheduledRootlastScheduledRoot以及他們的nextScheduleRoot屬性,說白了就是一個閉環鏈式結構 first => next => next => last(next => first), 同時更新rootexpirationTime屬性

function addRootToSchedule(root, expirationTime) {
   // root尚未開始過任務 將root加到schedule
  if (root.nextScheduledRoot === null) {
    ...
  } else {
    // root已經開始執行過任務, 更新root的expirationTime
    var remainingExpirationTime = root.expirationTime;
    if (remainingExpirationTime === NoWork || expirationTime < remainingExpirationTime) {
      root.expirationTime = expirationTime;
    }
  }
}

7.接着requestWork會判斷是否正在渲染中,防止重入。剩餘的工作將安排在當前渲染批次的末尾,如果正在渲染直接返回後, 因爲已經把root加上到Schedule裏面了,依然會把該root執行
同時判斷是否正在batch update, 這裏留到分析setState的時候說, 最後根據異步或者同步執行不同函數, 此處執行同步performSyncWork(),performSyncWork直接執行performWork(Sync, null);

function performWork(minExpirationTime, dl) {
  deadline = dl;
  // 找出優先級最高的root
  findHighestPriorityRoot();

  if (deadline !== null) {
    // ...異步
  } else {
    // 循環執行root任務
    while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || minExpirationTime >= nextFlushedExpirationTime)) {
      performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, true);
      findHighestPriorityRoot();
    }
  }
  ...
  // If there's work left over, schedule a new callback.
  if (nextFlushedExpirationTime !== NoWork) {
    scheduleCallbackWithExpirationTime(nextFlushedRoot, nextFlushedExpirationTime);
  }
 ...
}

8.performWork首先執行findHighestPriorityRoot函數。findHighestPriorityRoot函數主要執行兩個操作, 一個是判斷當前root是否還有任務,如果沒有, 則從firstScheuleRoot鏈中移除。 一個是找出優先級最高的root和其對應的優先級並賦值給
nextFlushedRootnextFlushedExpirationTime


function findHighestPriorityRoot() {
  var highestPriorityWork = NoWork;
  var highestPriorityRoot = null;
  if (lastScheduledRoot !== null) {
    var previousScheduledRoot = lastScheduledRoot;
    var root = firstScheduledRoot;
    while (root !== null) {
      var remainingExpirationTime = root.expirationTime;
      if (remainingExpirationTime === NoWork) {
         // 判斷是否還有任務並移除
      } else {
         // 找出最高的優先級root和其對應的優先級
      }
    }
  }
  // 賦值
  nextFlushedRoot = highestPriorityRoot;
  nextFlushedExpirationTime = highestPriorityWork;
}

9.緊着, performWork會根據傳入的參數dl來判斷進行同步或者異步操作, 這裏暫不討論異步,

while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || minExpirationTime >= nextFlushedExpirationTime)) {
      performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, true);
      findHighestPriorityRoot();
    }

10.接着, 會進行performWorkOnRoot函數, 並傳入優先級最高的root和其對應的expirationTime以及一個true作爲參數,performWorkOnRoot函數的第三個參數isExpired主要是用來判斷是否已超過執行時間, 由於進行的是同步操作, 所以默認超過
performWorkOnRoot函數會先將rendering狀態設爲true, 然後判斷是否異步或者超時進行操作

function performWorkOnRoot(root, expirationTime, isExpired) {
  // 將rendering狀態設爲true
  isRendering = true;

  // Check if this is async work or sync/expired work.
  if (deadline === null || isExpired) {
    // Flush work without yielding.
    // 同步
    var finishedWork = root.finishedWork;
    if (finishedWork !== null) {
      // This root is already complete. We can commit it.
      completeRoot(root, finishedWork, expirationTime);
    } else {
      root.finishedWork = null;
      // If this root previously suspended, clear its existing timeout, since
      // we're about to try rendering again.
      var timeoutHandle = root.timeoutHandle;
      if (enableSuspense && timeoutHandle !== noTimeout) {
        root.timeoutHandle = noTimeout;
        // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
        cancelTimeout(timeoutHandle);
      }
      var isYieldy = false;
      renderRoot(root, isYieldy, isExpired);
      finishedWork = root.finishedWork;
      if (finishedWork !== null) {
        // We've completed the root. Commit it.
        completeRoot(root, finishedWork, expirationTime);
      }
    }
  } else {
    // Flush async work.異步操作
    ......
    }
  }

  isRendering = false;
}

11.renderRoot的產物會掛載到rootfinishWork屬性上, 首先performWorkOnRoot會先判斷rootfinishWork是否不爲空, 如果存在的話則直接進入commit的階段, 否則進入到renderRoot函數, 設置finishWork屬性
renderRoot有三個參數, renderRoot(root, isYieldy, isExpired), 同步狀態下isYield的值是false,
renderRoot 先將 isWorking設爲true,

renderRoot會先判斷是否是一個從新開始的root, 是的話會重置各個屬性

首先是resetStach()函數, 對原有的進行中的root任務中斷, 進行存儲
緊接着將nextRootnextRendeExpirationTime重置, 同時創建第一個nextUnitOfWork, 也就是一個工作單元
這個nextUnitOfWork也是一個workProgress, 也是root.current的alternater屬性, 而它的alternate屬性則指向了root.current, 形成了一個雙緩衝池

if (expirationTime !== nextRenderExpirationTime || root !== nextRoot || nextUnitOfWork === null) {
    // 判斷是否是一個從新開始的root
    resetStack();
    nextRoot = root;
    nextRenderExpirationTime = expirationTime;
    nextUnitOfWork = createWorkInProgress(nextRoot.current, null, nextRenderExpirationTime);
    root.pendingCommitExpirationTime = NoWork;
    ....
    ....
  }

12.接着執行wookLoop(isYield)函數, 該函數通過循環執行, 遍歷每一個nextUniOfWork,


function workLoop(isYieldy) {
  if (!isYieldy) {
    // Flush work without yielding
    while (nextUnitOfWork !== null) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {
    // Flush asynchronous work until the deadline runs out of time.
    while (nextUnitOfWork !== null && !shouldYield()) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  }
}

13.performUnitOfWork 先 獲取 參數的alaernate屬性, 賦值給current,根據註釋的意思, workInProgress是作爲一個代替品存在來操作, 然後會執行下面這個語句

next = beginWork(current$$1, workInProgress, nextRenderExpirationTime);

14.beginWork主要根據workInprogresstag來做不同的處理, 並返回其child, 也就是下一個工作單元 如<div><p></p><div>, div作爲一個工作單元, 處理完後就返回工作單元p, 同時收集他們的effect

next存在, 則返回到workLoop函數繼續循環, 若不存在, 則執行completeUnitOfWork(workInProgress)函數

completeUnitOfWork函數, 會判斷是否有sibiling, 有則直接返回賦值給next, 否則判斷父fiber是否有sibiling, 一直循環到最上層父fiber爲null, 執行的同時會把effect逐級傳給父fiber

這個時候函數執行完畢, 會返回到renderRoot函數, renderRoot函數繼續往下走

首先將isWorking = false;執行, 然後會判斷nextUnitWork是否爲空, 否的話則將root.finishWork設爲空(異步, 該任務未執行完)並結束函數

isWorking = false;
if (nextUnitOfWork !== null) {
 onYield(root);
 return;
}

重置nextRoot

nextRoot = null;
interruptedBy = null;

賦值finishWork

var rootWorkInProgress = root.current.alternate;
onComplete(root, rootWorkInProgress, expirationTime);
function onComplete(root, finishedWork, expirationTime) {
  root.pendingCommitExpirationTime = expirationTime;
  root.finishedWork = finishedWork;
}

15.返回到performWorkOnRoot函數, 進入commit階段, 將rending狀態設爲false,返回到performWork函數, 繼續進入循環執行root, 直到所有root完成

重置各個狀態量, 如果還存在nextFlushedExpirationTime不爲空, 則進行scheduleCallbackWithExpirationTime函數異步操作

if (deadline !== null) {
    callbackExpirationTime = NoWork;
    callbackID = null;
  }
  // If there's work left over, schedule a new callback.
  if (nextFlushedExpirationTime !== NoWork) {
    scheduleCallbackWithExpirationTime(nextFlushedRoot, nextFlushedExpirationTime);
  }

  // Clean-up.
  deadline = null;
  deadlineDidExpire = false;

結語

以上就是同步模式下的源碼分析~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章