系列文章
React Fiber原始碼分析 第一篇
React Fiber原始碼分析 第二篇(同步模式)
React Fiber原始碼分析 第三篇(非同步狀態)
React Fiber原始碼分析 第四篇(歸納總結)
前言
React Fiber是React在V16版本中的大更新,利用了閒餘時間看了一些原始碼,做個小記錄~
如果有錯誤,請輕噴
流程圖
流程圖1
流程圖2
原始碼分析
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即為更新的物件, 然後執行enqueuUpdate, enqueueUpdate相對比較容易理解, 不過裡面有一註釋挺重要
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以及上層fiber的childrenExpirationTime
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, 即設定firstScheduledRoot, lastScheduledRoot以及他們的nextScheduleRoot屬性,說白了就是一個閉環鏈式結構 first => next => next => last(next => first), 同時更新root的expirationTime屬性
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的產物會掛載到root的finishWork屬性上, 首先performWorkOnRoot會先判斷root的finishWork是否不為空, 如果存在的話則直接進入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主要根據workInprogress的tag來做不同的處理, 並返回其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;
結語
以上就是同步模式下的原始碼分析~