React Fiber源碼分析 第一篇
React Fiber源碼分析 第二篇(同步模式)
React Fiber源碼分析 第三篇(異步狀態)
React Fiber源碼分析 第四篇(概括總結)node
React Fiber是React在V16版本中的大更新,利用了閒餘時間看了一些源碼,作個小記錄~bash
1.調用setState時, 會調用classComponentUpdater的enqueueSetState方法, 同時將新的state做爲payload參數傳進enqueueSetState會先調用requestCurrentTime獲取一個currentTime異步
function requestCurrentTime() {
// 維護兩個時間 一個renderingTime 一個currentSechedulerTime
// rederingTime 能夠隨時更新 currentSechedulerTime只有在沒有新任務的時候才更新
if (isRendering) {
return currentSchedulerTime;
}
findHighestPriorityRoot();
if (nextFlushedExpirationTime === NoWork || nextFlushedExpirationTime === Never) {
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;
return currentSchedulerTime;
}
return currentSheculerTime
複製代碼
2.經過獲取到的currentTime, 調用computeExpirationForFiber,計算該fiber的優先級async
if (fiber.mode & AsyncMode) {
if (isBatchingInteractiveUpdates) {
// This is an interactive update
expirationTime = computeInteractiveExpiration(currentTime);
} else {
// This is an async update
expirationTime = computeAsyncExpiration(currentTime);
}
...
}
複製代碼
3.這個函數其餘點比較簡單, 裏面主要有下面 這個判斷要說明一下, 若是是屬於異步更新的話,會根據是 交互引發的更新 仍是其餘更新 來調用不一樣的函數computeInteractiveExpiration和computeAsyncExpiration,函數
能夠看到這兩個函數最後返回的都是computeExpirationBucket函數的結果, 只是入參不一樣, computeInteractiveExpiration的參數是500, 100, computeAsyncExpiration的參數是5000, 250, 而後看computeExpirationBucket函數能夠看到, 第二個參數(500和5000)越大,則返回的expirationTime越大, 也就是說 computeInteractiveExpiration的更新優先級高於computeAsyncExpiration, 則交互的優先級高於其餘oop
得到優先級後則和同步更新同樣, 建立update並放進隊列, 而後調用sheuduleWork源碼分析
var classComponentUpdater = {
isMounted: isMounted,
enqueueSetState: function (inst, payload, callback) {
var fiber = get(inst);
   // 得到優先級
var currentTime = requestCurrentTime();
var expirationTime = computeExpirationForFiber(currentTime, fiber);
   // 建立更新
var update = createUpdate(expirationTime);
update.payload = payload;
if (callback !== undefined && callback !== null) {
update.callback = callback;
}
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
},
複製代碼
4.接下來的步驟和同步同樣, 直到同步調用的是performSyncWork函數, 而異步調用的是scheduleCallbackWithExpirationTime函數post
scheduleCallbackWithExpirationTime函數首先判斷是否存在callback正在進行中, 判斷現有expirationTime和其優先級,若優先級比較低則直接返回, 不然設置如今的fiber任務爲新的callback,並把原來的回調從列表中移除ui
function scheduleCallbackWithExpirationTime(root, expirationTime) {
if (callbackExpirationTime !== NoWork) {
// 判斷優先級
if (expirationTime > callbackExpirationTime) {
// Existing callback has sufficient timeout. Exit.
return;
} else {
if (callbackID !== null) {
// 取消, 從回調列表中刪除
schedule.unstable_cancelScheduledWork(callbackID);
}
}
// The request callback timer is already running. Don't start a new one. } // 設置新的callback和callbackExiporationTime callbackExpirationTime = expirationTime; var currentMs = schedule.unstable_now() - originalStartTimeMs; var expirationTimeMs = expirationTimeToMs(expirationTime); // 計算是否超時 var timeout = expirationTimeMs - currentMs; callbackID = schedule.unstable_scheduleWork(performAsyncWork, { timeout: timeout }); } 複製代碼
5.接下來調用schedule.unstable_scheduleWork(performAsyncWork, { timeout: timeout })函數, 並生成一個節點, 存儲回調函數和超時時間,插入到回調列表, 並根據超時排序, 調用ensureHostCallBackIsScheduled函數,最後返回該節點this
function unstable_scheduleWork(callback, options) {
var currentTime = exports.unstable_now();
var timesOutAt;
// 獲取超時時間
if (options !== undefined && options !== null && options.timeout !== null && options.timeout !== undefined) {
// Check for an explicit timeout
timesOutAt = currentTime + options.timeout;
} else {
// Compute an absolute timeout using the default constant.
timesOutAt = currentTime + DEFERRED_TIMEOUT;
}
 // 生成一個節點, 存儲回調函數和超時時間
var newNode = {
callback: callback,
timesOutAt: timesOutAt,
next: null,
previous: null
};
// 插入到回調列表, 並根據超時排序, 最後返回該節點
if (firstCallbackNode === null) {
// This is the first callback in the list.
firstCallbackNode = newNode.next = newNode.previous = newNode;
ensureHostCallbackIsScheduled(firstCallbackNode);
} else {
...var previous = next.previous;
previous.next = next.previous = newNode;
newNode.next = next;
newNode.previous = previous;
}
return newNode;
}
複製代碼
6.ensureHostCallBackIsScheduled函數如名, 相對比較簡單
function ensureHostCallbackIsScheduled() {
if (isPerformingWork) {
// Don't schedule work yet; wait until the next time we yield. return; } // Schedule the host callback using the earliest timeout in the list. var timesOutAt = firstCallbackNode.timesOutAt; if (!isHostCallbackScheduled) { isHostCallbackScheduled = true; } else { // Cancel the existing host callback. cancelCallback(); } requestCallback(flushWork, timesOutAt); } 複製代碼
7.往下看requestCallback, 這裏說的若是已經在執行任務的話, 就必須有一個錯誤被拋出(拋出的錯誤是啥??),同時不要等待下一幀, 儘快開始新事件
若是若是當前沒有調度幀回調函數,咱們須要進行一個調度幀回調函數, 並設置isAnimationFrameScheduled爲true, 接着執行requestAnimationFrameWithTimeout;函數
requestCallback = function (callback, absoluteTimeout) {
scheduledCallback = callback;
timeoutTime = absoluteTimeout;
if (isPerformingIdleWork) {
// 若是已經在執行任務的話, 就必須有一個錯誤被拋出(拋出的錯誤是啥??),同時不要等待下一幀, 儘快開始新事件
window.postMessage(messageKey, '*');
} else if (!isAnimationFrameScheduled) {
isAnimationFrameScheduled = true;
requestAnimationFrameWithTimeout(animationTick);
}
};
複製代碼
8.requestAnimationFrameWithTimeout函數就是執行一個異步操做, 執行完畢後, 假設此時又有N個回調任務進入, 同時原來的回調尚未進行, 則回到scheduleCallbackWithExpirationTime函數上,
分爲兩個分支:
到了這時候, 已經把新的回調替換正在進行的回調到回調列表。 函數正常執行, 調用callback, 即animationTick函數
cancelCallback = function () {
scheduledCallback = null;
isIdleScheduled = false;
timeoutTime = -1;
};
複製代碼
var ANIMATION_FRAME_TIMEOUT = 100;
var rAFID;
var rAFTimeoutID;
var requestAnimationFrameWithTimeout = function (callback) {
// schedule rAF and also a setTimeout
rAFID = localRequestAnimationFrame(function (timestamp) {
// cancel the setTimeout
localClearTimeout(rAFTimeoutID);
callback(timestamp);
});
rAFTimeoutID = localSetTimeout(function () {
// cancel the requestAnimationFrame
localCancelAnimationFrame(rAFID);
callback(exports.unstable_now());
}, ANIMATION_FRAME_TIMEOUT);
};
複製代碼
9.animationTick一個是把isAnimationFrameScheduled狀態設爲false, 即不在調度幀回調的狀態, 同時計算幀到期時間frameDeadline , 判斷是否在幀回調的狀態, 否的話調用window.postMessage ,並設置isIdleScheduled狀態爲true
假設此時, 有N個回調進入, 分爲兩個狀況:
1.假設優先級低於目前的回調任務, 則直接返回(已經把root加到root隊列中)
2.優先級高於目前的回調任務, 將目前的回調任務從列表中移除, 並將callBackID設爲傳入的回調, 接下來的路線與上面一致,一直到animationTick函數,由於 postMessage比setTImeout更快執行,因此此時isIdleScheduled爲false,和以前同樣正常執行。
var animationTick = function (rafTime) {
isAnimationFrameScheduled = false;
...
...
// 每幀到期時間爲33ms
frameDeadline = rafTime + activeFrameTime;
if (!isIdleScheduled) {
isIdleScheduled = true;
window.postMessage(messageKey, '*');
}
};
複製代碼
10.postMessage會執行idleTick , 首先把isIdleScheduled\didTimeout置爲false,
先判斷幀到期時間和超時時間是否小於當前時間, 若是是的話, 則置didTimeout爲true, 若是幀到期, 但超時時間小於當前時間, 則置isAnimationFrameScheduled 爲false, 並調用requestAnimationFrameWithTimeout, 即進入下一幀 若是幀未到期, 則調用callbak函數, 並把isPerformingIdleWork置爲true
idleTick 會先執行callback, 完成後纔將isPerformingIdleWork 置爲false, 執行callback的時候會傳入didTimeout做爲參數, callback爲flushWork
var idleTick = function (event) {
...
isIdleScheduled = false;
var currentTime = exports.unstable_now();
var didTimeout = false;
if (frameDeadline - currentTime <= 0) {
// 幀過時
if (timeoutTime !== -1 && timeoutTime <= currentTime) {
// 回調超時
didTimeout = true;
} else {
// No timeout.
if (!isAnimationFrameScheduled) {
// 到下一幀繼續任務
isAnimationFrameScheduled = true;
requestAnimationFrameWithTimeout(animationTick);
}
// Exit without invoking the callback.
return;
}
}
timeoutTime = -1;
var callback = scheduledCallback;
scheduledCallback = null;
if (callback !== null) {
isPerformingIdleWork = true;
try {
callback(didTimeout);
} finally {
isPerformingIdleWork = false;
}
}
};
複製代碼
11.flushwork首先把isPerformingWork置爲true, 而後把didTimeout賦值給deallinObject對象, 接下來進行判斷 若是已通過了幀的結束期, 則判斷鏈表中有哪一個節點已超時, 並循環調用flushFirstCallback函數解決超時節點, 若是尚未過幀的結束期, 則調用flushFirstCallback函數處理鏈表中的第一個節點, 循環處理一直到該幀結束
最後, flushwork函數會將isPerformingWork置爲false, 並判斷是否還有任務 有則執行ensureHostCallbackIsScheduled函數
function flushWork(didTimeout) {
isPerformingWork = true;
deadlineObject.didTimeout = didTimeout;
try {
if (didTimeout) {
while (firstCallbackNode !== null) {
var currentTime = exports.unstable_now();
if (firstCallbackNode.timesOutAt <= currentTime) {
do {
flushFirstCallback();
} while (firstCallbackNode !== null && firstCallbackNode.timesOutAt <= currentTime);
continue;
}
break;
}
} else {
// Keep flushing callbacks until we run out of time in the frame.
if (firstCallbackNode !== null) {
do {
flushFirstCallback();
} while (firstCallbackNode !== null && getFrameDeadline() - exports.unstable_now() > 0);
}
}
} finally {
isPerformingWork = false;
if (firstCallbackNode !== null) {
// There's still work remaining. Request another callback. ensureHostCallbackIsScheduled(firstCallbackNode); } else { isHostCallbackScheduled = false; } } } 複製代碼
12.繼續往下看, 則是flushFirstCallback函數,先把該節點從鏈表中清掉, 而後調用callback函數, 並帶入deadlineObject做爲參數
function flushFirstCallback(node) {
var flushedNode = firstCallbackNode;
//從鏈表中清理掉該節點, 這樣哪怕出錯了, 也能保留原鏈表狀態
var next = firstCallbackNode.next;
if (firstCallbackNode === next) {
// This is the last callback in the list.
firstCallbackNode = null;
next = null;
} else {
var previous = firstCallbackNode.previous;
firstCallbackNode = previous.next = next;
next.previous = previous;
}
flushedNode.next = flushedNode.previous = null;
// Now it's safe to call the callback. var callback = flushedNode.callback; callback(deadlineObject); } 複製代碼
13.接下來的就是performAsyncWork函數,若是didTimeout爲true, 則代表至少有一個更新已過時, 迭代全部root任務, 把已過時的root的nextExpirationTimeToWorkOn重置爲當前時間currentTime. 而後調用performWork函數
function performAsyncWork(dl) {
if (dl.didTimeout) {
// 刷新全部root的nextEpirationTimeToWorkOn
if (firstScheduledRoot !== null) {
recomputeCurrentRendererTime();
var root = firstScheduledRoot;
do {
didExpireAtExpirationTime(root, currentRendererTime);
// The root schedule is circular, so this is never null.
root = root.nextScheduledRoot;
} while (root !== firstScheduledRoot);
}
}
performWork(NoWork, dl);
}
複製代碼
14.performWork函數在以前已經分析過了, 這裏主要看存在deadline時的操做, 在幀未到期 或者 當前渲染時間大於等於nextFlushedExpirationTime時才執行 performWorkOnRoot, 並將currentRendererTime >= nextFlushedExpirationTime做爲第三個參數傳入, 一直循環處理任務, 最後清除callbackExpirationTime, callBackId, 同時, 若是還有任務的話, 則繼續調用scheduleCallbackWithExpirationTime(nextFlushedRoot, nextFlushedExpirationTime);函數進入到回調
function performWork(minExpirationTime, dl) {
deadline = dl;
// Keep working on roots until there's no more work, or until we reach // the deadline. findHighestPriorityRoot(); if (deadline !== null) { recomputeCurrentRendererTime(); currentSchedulerTime = currentRendererTime;while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || minExpirationTime >= nextFlushedExpirationTime) && (!deadlineDidExpire || currentRendererTime >= nextFlushedExpirationTime)) { performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, currentRendererTime >= nextFlushedExpirationTime); findHighestPriorityRoot(); recomputeCurrentRendererTime(); currentSchedulerTime = currentRendererTime; } } 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;
finishRendering();
}
複製代碼
15.接下來看異步狀態下的performWorkOnRoot函數。基本操做和同步同樣, 在進入到renderRoot(root, _isYieldy, isExpired);函數時, 會根據是否已超時將isYieldy置爲true或者false, 異步狀態下未超時爲false, renderRoot和同步同樣, 最後執行workLoop(isYieldy) workLoop在未過時的狀況下, 會執行shouldYield()函數來判斷是否執行nextUnitOfWork, 和同步同樣, 這裏只須要關注shouldYied函數
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);
}
}
}
複製代碼
16.shouldYield函數, 若是deadlineDidExpire爲true, 即幀已到期, 直接返回true, 若是deadline不存在, 而且幀未到期, 則返回false, 能夠執行單元 不然將deadlineDidExpire置爲true
function shouldYield() {
if (deadlineDidExpire) {
return true;
}
if (deadline === null || deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
// Disregard deadline.didTimeout. Only expired work should be flushed
// during a timeout. This path is only hit for non-expired work.
return false;
}
deadlineDidExpire = true;
return true;
}
複製代碼
源碼分析到這裏就結束啦,下一篇作一個總結,否則就是流水帳同樣的,容易忘記