從本篇開始,咱們就正式進入React的核心調度算法—Fiber調度機制。javascript
前言:
你須要知道:淺談React 16中的Fiber機制、React源碼解析之RootFiber、React源碼解析之FiberRootphp
在以前的文章中講到,React更新的方式有三種:
(1)ReactDOM.render() || hydrate(ReactDOMServer渲染)
(2)setState()
(3)forceUpdate()java
在createUpdate
後就進入scheduleWork
流程,接下來咱們就正式進入調度流程node
1、scheduleUpdateOnFiber()
做用:
調度update
任務react
提示:scheduleWork
即scheduleUpdateOnFiber
:git
export const scheduleWork = scheduleUpdateOnFiber;
複製代碼
源碼:github
//scheduleWork
export function scheduleUpdateOnFiber(
fiber: Fiber,
expirationTime: ExpirationTime,
) {
//判斷是不是無限循環update
checkForNestedUpdates();
//測試環境用的,不看
warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
//找到rootFiber並遍歷更新子節點的expirationTime
const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
if (root === null) {
warnAboutUpdateOnUnmountedFiberInDEV(fiber);
return;
}
//NoWork表示無更新操做
root.pingTime = NoWork;
//判斷是否有高優先級任務打斷當前正在執行的任務
checkForInterruption(fiber, expirationTime);
//報告調度更新,測試環境用的,可不看
recordScheduleUpdate();
// TODO: computeExpirationForFiber also reads the priority. Pass the
// priority as an argument to that function and this one.
const priorityLevel = getCurrentPriorityLevel();
//1073741823
//若是expirationTime等於最大整型值的話
//若是是同步任務的過時時間的話
if (expirationTime === Sync) {
//若是還未渲染,update是未分批次的,
//也就是第一次渲染前
if (
// Check if we're inside unbatchedUpdates
(executionContext & LegacyUnbatchedContext) !== NoContext &&
// Check if we're not already rendering
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// Register pending interactions on the root to avoid losing traced interaction data.
//跟蹤這些update,並計數、檢測它們是否會報錯
schedulePendingInteractions(root, expirationTime);
// This is a legacy edge case. The initial mount of a ReactDOM.render-ed
// root inside of batchedUpdates should be synchronous, but layout updates
// should be deferred until the end of the batch.
//批量更新時,render是要保持同步的,但佈局的更新要延遲到批量更新的末尾才執行
//初始化root
//調用workLoop進行循環單元更新
let callback = renderRoot(root, Sync, true);
while (callback !== null) {
callback = callback(true);
}
}
//render後
else {
//當即執行調度任務
scheduleCallbackForRoot(root, ImmediatePriority, Sync);
//當前沒有update時
if (executionContext === NoContext) {
// Flush the synchronous work now, wnless we're already working or inside
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
// scheduleCallbackForFiber to preserve the ability to schedule a callback
// without immediately flushing it. We only do this for user-initated
// updates, to preserve historical behavior of sync mode.
//刷新同步任務隊列
flushSyncCallbackQueue();
}
}
}
//若是是異步任務的話,則當即執行調度任務
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);
}
}
}
}
複製代碼
解析:
有點長,咱們慢慢看↓
web
2、checkForNestedUpdates()
做用:
判斷是不是無限循環的update
算法
源碼:sql
// Use these to prevent an infinite loop of nested updates
const NESTED_UPDATE_LIMIT = 50;
let nestedUpdateCount: number = 0;
//防止無限循環地嵌套更新
function checkForNestedUpdates() {
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
nestedUpdateCount = 0;
rootWithNestedUpdates = null;
invariant(
false,
'Maximum update depth exceeded. This can happen when a component ' +
'repeatedly calls setState inside componentWillUpdate or ' +
'componentDidUpdate. React limits the number of nested updates to ' +
'prevent infinite loops.',
);
}
}
複製代碼
解析:
超過 50層嵌套update
,就終止進行調度,並報出error
常見的形成死循環的爲兩種狀況:
① 在render()
中無條件調用setState()
注意:
有條件調用setState()
的話,是能夠放在render()
中的
render(){
if(xxx){
this.setState({ yyy })
}
}
複製代碼
② 以下圖,在shouldComponentUpdate()
和componentWillUpdate()
中調用setState()
3、markUpdateTimeFromFiberToRoot()
做用:
找到rootFiber
並遍歷更新子節點的expirationTime
源碼:
//目標fiber會向上尋找rootFiber對象,在尋找的過程當中會進行一些操做
function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
// Update the source fiber's expiration time
//若是fiber對象的過時時間小於 expirationTime,則更新fiber對象的過時時間
//也就是說,當前fiber的優先級是小於expirationTime的優先級的,如今要調高fiber的優先級
if (fiber.expirationTime < expirationTime) {
fiber.expirationTime = expirationTime;
}
//在enqueueUpdate()中有講到,與fiber.current是映射關係
let alternate = fiber.alternate;
//同上
if (alternate !== null && alternate.expirationTime < expirationTime) {
alternate.expirationTime = expirationTime;
}
// Walk the parent path to the root and update the child expiration time.
//向上遍歷父節點,直到root節點,在遍歷的過程當中更新子節點的expirationTime
//fiber的父節點
let node = fiber.return;
let root = null;
//node=null,表示是沒有父節點了,也就是到達了RootFiber,即最大父節點
//HostRoot即樹的頂端節點root
if (node === null && fiber.tag === HostRoot) {
//RootFiber的stateNode就是FiberRoot
root = fiber.stateNode;
}
//沒有到達FiberRoot的話,則進行循環
else {
while (node !== null) {
alternate = node.alternate;
//若是父節點的全部子節點中優先級最高的更新時間仍小於expirationTime的話
//則提升優先級
if (node.childExpirationTime < expirationTime) {
//從新賦值
node.childExpirationTime = expirationTime;
//alternate是相對於fiber的另外一個對象,也要進行更新
if (
alternate !== null &&
alternate.childExpirationTime < expirationTime
) {
alternate.childExpirationTime = expirationTime;
}
}
//別看差了是對應(node.childExpirationTime < expirationTime)的if
else if (
alternate !== null &&
alternate.childExpirationTime < expirationTime
) {
alternate.childExpirationTime = expirationTime;
}
//若是找到頂端rootFiber,結束循環
if (node.return === null && node.tag === HostRoot) {
root = node.stateNode;
break;
}
node = node.return;
}
}
//更新該rootFiber的最舊、最新的掛起時間
if (root !== null) {
// Update the first and last pending expiration times in this root
const firstPendingTime = root.firstPendingTime;
if (expirationTime > firstPendingTime) {
root.firstPendingTime = expirationTime;
}
const lastPendingTime = root.lastPendingTime;
if (lastPendingTime === NoWork || expirationTime < lastPendingTime) {
root.lastPendingTime = expirationTime;
}
}
return root;
}
複製代碼
解析:markUpdateTimeFromFiberToRoot()
主要作了如下事情
(1)更新fiber
對象的expirationTime
(2)根據fiber.return
向上遍歷尋找RootFiber
(fiber的頂層對象)
(3)在向上遍歷的過程當中,更新父對象fiber.return
子節點的childExpirationTime
關於RootFiber
,請參考:React源碼解析之RootFiber
(4)找到RootFiber
後,根據RootFiber.stateNode=FiberRoot
的關係,找到FiberRoot
(5)更新該rootFiber
的最舊、最新的掛起時間
(6)返回RootFiber
4、checkForInterruption()
做用:
判斷是否有高優先級任務打斷當前正在執行的任務
源碼:
//判斷是否有高優先級任務打斷當前正在執行的任務
function checkForInterruption(
fiberThatReceivedUpdate: Fiber,
updateExpirationTime: ExpirationTime,
) {
//若是任務正在執行,而且異步任務已經執行到一半了,
//可是如今須要把執行權交給瀏覽器,去執行優先級更高的任務
if (
enableUserTimingAPI &&
workInProgressRoot !== null &&
updateExpirationTime > renderExpirationTime
) {
//打斷當前任務,優先執行新的update
interruptedBy = fiberThatReceivedUpdate;
}
}
複製代碼
解析:
若是當前fiber
的優先級更高,須要打斷當前執行的任務,當即執行該fiber
上的update
,則更新interruptedBy
5、getCurrentPriorityLevel()
做用:
獲取當前調度任務的優先級
源碼:
// Except for NoPriority, these correspond to Scheduler priorities. We use
// ascending numbers so we can compare them like numbers. They start at 90 to
// avoid clashing with Scheduler's priorities.
//除了90,用數字是由於這樣作,方便比較
//從90開始的緣由是防止和Scheduler的優先級衝突
export const ImmediatePriority: ReactPriorityLevel = 99;
export const UserBlockingPriority: ReactPriorityLevel = 98;
export const NormalPriority: ReactPriorityLevel = 97;
export const LowPriority: ReactPriorityLevel = 96;
export const IdlePriority: ReactPriorityLevel = 95;
// NoPriority is the absence of priority. Also React-only.
export const NoPriority: ReactPriorityLevel = 90;
//獲取當前調度任務的優先級
export function getCurrentPriorityLevel(): ReactPriorityLevel {
switch (Scheduler_getCurrentPriorityLevel()) {
//99
case Scheduler_ImmediatePriority:
return ImmediatePriority;
//98
case Scheduler_UserBlockingPriority:
return UserBlockingPriority;
//97
case Scheduler_NormalPriority:
return NormalPriority;
//96
case Scheduler_LowPriority:
return LowPriority;
//95
case Scheduler_IdlePriority:
return IdlePriority;
default:
invariant(false, 'Unknown priority level.');
}
}
複製代碼
Scheduler_getCurrentPriorityLevel():
const {
unstable_getCurrentPriorityLevel: Scheduler_getCurrentPriorityLevel,
} = Scheduler;
function unstable_getCurrentPriorityLevel() {
return currentPriorityLevel;
}
//當前調度優先級默認爲 NormalPriority
var currentPriorityLevel = NormalPriority;
複製代碼
解析:
記住scheduler
優先級是90往上,而且默認是NormalPriority
(97)
若是是同步任務,而且是初次render()的話,會先執行schedulePendingInteractions()
6、schedulePendingInteractions()
做用:
跟蹤須要同步執行的update
們,並計數、檢測它們是否會報錯
源碼:
schedulePendingInteractions():
//跟蹤這些update,並計數、檢測它們是否會報錯
function schedulePendingInteractions(root, expirationTime) {
// This is called when work is scheduled on a root.
// It associates the current interactions with the newly-scheduled expiration.
// They will be restored when that expiration is later committed.
//當調度開始時就執行,每調度一個update,就更新跟蹤棧
if (!enableSchedulerTracing) {
return;
}
//調度的"交互"
scheduleInteractions(root, expirationTime, __interactionsRef.current);
}
複製代碼
__interactionsRef:
// Set of currently traced interactions.
// Interactions "stack"–
// Meaning that newly traced interactions are appended to the previously active set.
// When an interaction goes out of scope, the previous set (if any) is restored.
//設置當前跟蹤的interactions,也是interactions的棧
//它是一個集合
let interactionsRef: InteractionsRef = (null: any);
複製代碼
scheduleInteractions():
//與schedule的交互
function scheduleInteractions(root, expirationTime, interactions) {
if (!enableSchedulerTracing) {
return;
}
//當interactions存在時
if (interactions.size > 0) {
//獲取FiberRoot的pendingInteractionMap屬性
const pendingInteractionMap = root.pendingInteractionMap;
//獲取pendingInteractions的expirationTime
const pendingInteractions = pendingInteractionMap.get(expirationTime);
//若是pendingInteractions不爲空的話
if (pendingInteractions != null) {
//遍歷並更新還未調度的同步任務的數量
interactions.forEach(interaction => {
if (!pendingInteractions.has(interaction)) {
// Update the pending async work count for previously unscheduled interaction.
interaction.__count++;
}
pendingInteractions.add(interaction);
});
}
//不然初始化pendingInteractionMap
//並統計當前調度中同步任務的數量
else {
pendingInteractionMap.set(expirationTime, new Set(interactions));
// Update the pending async work count for the current interactions.
interactions.forEach(interaction => {
interaction.__count++;
});
}
//計算並得出線程的id
const subscriber = __subscriberRef.current;
if (subscriber !== null) {
//這個暫時不看了
const threadID = computeThreadID(root, expirationTime);
//檢測這些任務是否會報錯
subscriber.onWorkScheduled(interactions, threadID);
}
}
}
複製代碼
subscriber.onWorkScheduled():
//利用線程去檢測同步的update,判斷它們是否會報錯
function onWorkScheduled(
interactions: Set<Interaction>,
threadID: number,
): void {
let didCatchError = false;
let caughtError = null;
//遍歷去檢測
subscribers.forEach(subscriber => {
try {
subscriber.onWorkScheduled(interactions, threadID);
} catch (error) {
if (!didCatchError) {
didCatchError = true;
caughtError = error;
}
}
});
if (didCatchError) {
throw caughtError;
}
}
複製代碼
解析:
利用FiberRoot
的pendingInteractionMap
屬性和不一樣的expirationTime
,獲取每次schedule
所需的update
任務的集合,記錄它們的數量,並檢測這些任務是否會出錯。
7、renderRoot()
做用:
初始化root,並調用workLoop進行循環單元更新
這個咱們放在後面再講。
後言:
原本是寫到十一了,但發現還有一層套一層的function
,所有放在一篇文章裏的話,太長了,容易消化不了,因此咱們先在這裏打住:
export function scheduleUpdateOnFiber(){
xxx
xxx
xxx
if (expirationTime === Sync) {
if( 第一次render ){
//跟蹤這些update,並計數、檢測它們是否會報錯
schedulePendingInteractions(root, expirationTime);
//初始化root,調用workLoop進行循環單元更新
let callback = renderRoot(root, Sync, true);
}else{
下篇要講的內容。。。。
}
}
}
複製代碼
scheduleWork 結束後,我會像往常同樣,製做一張流程圖幫助你們梳理思路!
源碼請參考GitHub:
github.com/AttackXiaoJ…
(未完待續!)