上篇回顧:
React源碼解析之scheduleWork(上)javascript
8、scheduleCallbackForRoot()
做用:
在render()
以後,當即執行調度任務java
源碼:node
// Use this function, along with runRootCallback, to ensure that only a single
// callback per root is scheduled. It's still possible to call renderRoot
// directly, but scheduling via this function helps avoid excessive callbacks.
// It works by storing the callback node and expiration time on the root. When a
// new callback comes in, it compares the expiration time to determine if it
// should cancel the previous one. It also relies on commitRoot scheduling a
// callback to render the next level, because that means we don't need a
// separate callback per expiration time.
//同步調用callback
//流程是在root上存取callback和expirationTime,
// 當新的callback調用時,比較更新expirationTime
function scheduleCallbackForRoot(
root: FiberRoot,
priorityLevel: ReactPriorityLevel,
expirationTime: ExpirationTime,
) {
//獲取root的回調過時時間
const existingCallbackExpirationTime = root.callbackExpirationTime;
//更新root的回調過時時間
if (existingCallbackExpirationTime < expirationTime) {
// New callback has higher priority than the existing one.
//當新的expirationTime比已存在的callback的expirationTime優先級更高的時候
const existingCallbackNode = root.callbackNode;
if (existingCallbackNode !== null) {
//取消已存在的callback(打斷)
//將已存在的callback節點從鏈表中移除
cancelCallback(existingCallbackNode);
}
//更新callbackExpirationTime
root.callbackExpirationTime = expirationTime;
//若是是同步任務
if (expirationTime === Sync) {
// Sync React callbacks are scheduled on a special internal queue
//在臨時隊列中同步被調度的callback
root.callbackNode = scheduleSyncCallback(
runRootCallback.bind(
null,
root,
renderRoot.bind(null, root, expirationTime),
),
);
} else {
let options = null;
if (expirationTime !== Never) {
//(Sync-2 - expirationTime) * 10-now()
let timeout = expirationTimeToMs(expirationTime) - now();
options = {timeout};
}
//callbackNode即通過處理包裝的新task
root.callbackNode = scheduleCallback(
priorityLevel,
//bind()的意思是綁定this,xx.bind(y)()這樣纔算執行
runRootCallback.bind(
null,
root,
renderRoot.bind(null, root, expirationTime),
),
options,
);
if (
enableUserTimingAPI &&
expirationTime !== Sync &&
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// Scheduled an async callback, and we're not already working. Add an
// entry to the flamegraph that shows we're waiting for a callback
// to fire.
//開始調度callback的標誌
startRequestCallbackTimer();
}
}
}
// Associate the current interactions with this new root+priority.
//跟蹤這些update,並計數、檢測它們是否會報錯
schedulePendingInteractions(root, expirationTime);
}
複製代碼
解析:
Fiber機制能夠爲每個update任務進行優先級排序,而且能夠記錄調度到了哪裏(schedulePendingInteractions())react
同時,還能夠中斷正在執行的任務,優先執行優先級比當前高的任務(scheduleCallbackForRoot()
),以後,還能夠繼續以前中斷的任務,而React16 以前調用setState()
,必須等待setState
的update
隊列所有調度完,才能進行以後的操做。git
一塊兒看下scheduleCallbackForRoot()
作了什麼:
(1)當新的scheduleCallback
的優先級更高時,中斷當前任務cancelCallback(existingCallbackNode)
(2)若是是同步任務,則在臨時隊列中進行調度
(3)若是是異步任務,則更新調度隊列的狀態
(4)設置開始調度的時間節點
(5)跟蹤調度的任務github
具體講解,請耐心往下看web
9、cancelCallback()
做用:
中斷正在執行的調度任務app
源碼:異步
const {
unstable_cancelCallback: Scheduler_cancelCallback,
} = Scheduler;
//從鏈表中移除task節點
function unstable_cancelCallback(task) {
//獲取callbackNode的next節點
var next = task.next;
//因爲鏈表是雙向循環鏈表,一旦next是null則證實該節點已不存在於鏈表中
if (next === null) {
// Already cancelled.
return;
}
//本身等於本身,說明鏈表中就這一個callback節點
//firstTask/firstDelayedTask應該是相似遊標的概念,即正要執行的節點
if (task === next) {
//置爲null,即刪除callback節點
//重置firstTask/firstDelayedTask
if (task === firstTask) {
firstTask = null;
} else if (task === firstDelayedTask) {
firstDelayedTask = null;
}
} else {
//將firstTask/firstDelayedTask指向下一節點
if (task === firstTask) {
firstTask = next;
} else if (task === firstDelayedTask) {
firstDelayedTask = next;
}
var previous = task.previous;
//熟悉的鏈表操做,刪除已存在的callbackNode
previous.next = next;
next.previous = previous;
}
task.next = task.previous = null;
}
複製代碼
解析:
操做schedule
鏈表,將正要執行的callback
「移除」,將遊標指向下一個調度任務async
10、scheduleSyncCallback()
做用:
若是是同步任務的話,則執行scheduleSyncCallback()
,將調度任務入隊,並返回入隊後的臨時隊列
源碼:
//入隊callback,並返回臨時的隊列
export function scheduleSyncCallback(callback: SchedulerCallback) {
// Push this callback into an internal queue. We'll flush these either in
// the next tick, or earlier if something calls `flushSyncCallbackQueue`.
//在下次調度或調用 刷新同步回調隊列 的時候刷新callback隊列
//若是同步隊列爲空的話,則初始化同步隊列,
//並在下次調度的一開始就刷新隊列
if (syncQueue === null) {
syncQueue = [callback];
// Flush the queue in the next tick, at the earliest.
immediateQueueCallbackNode = Scheduler_scheduleCallback(
//賦予調度當即執行的高權限
Scheduler_ImmediatePriority,
flushSyncCallbackQueueImpl,
);
}
//若是同步隊列不爲空的話,則將callback入隊
else {
// Push onto existing queue. Don't need to schedule a callback because
// we already scheduled one when we created the queue.
//在入隊的時候,沒必要去調度callback,由於在建立隊列的時候就已經調度了
syncQueue.push(callback);
}
//fake我認爲是臨時隊列的意思
return fakeCallbackNode;
}
複製代碼
解析:
(1)當同步隊列爲空
調用Scheduler_scheduleCallback()
,將該callback
任務入隊,並把該callback
包裝成newTask
,賦給root.callbackNode
Scheduler_scheduleCallback():
const {
unstable_scheduleCallback: Scheduler_scheduleCallback,
} = Scheduler;
//返回通過包裝處理的task
function unstable_scheduleCallback(priorityLevel, callback, options) {
var currentTime = getCurrentTime();
var startTime;
var timeout;
//更新startTime(默認是如今)和timeout(默認5s)
if (typeof options === 'object' && options !== null) {
var delay = options.delay;
if (typeof delay === 'number' && delay > 0) {
startTime = currentTime + delay;
} else {
startTime = currentTime;
}
timeout =
typeof options.timeout === 'number'
? options.timeout
: timeoutForPriorityLevel(priorityLevel);
} else {
// Times out immediately
// var IMMEDIATE_PRIORITY_TIMEOUT = -1;
// Eventually times out
// var USER_BLOCKING_PRIORITY = 250;
//普通優先級的過時時間是5s
// var NORMAL_PRIORITY_TIMEOUT = 5000;
//低優先級的過時時間是10s
// var LOW_PRIORITY_TIMEOUT = 10000;
timeout = timeoutForPriorityLevel(priorityLevel);
startTime = currentTime;
}
//過時時間是當前時間+5s,也就是默認是5s後,react進行更新
var expirationTime = startTime + timeout;
//封裝成新的任務
var newTask = {
callback,
priorityLevel,
startTime,
expirationTime,
next: null,
previous: null,
};
//若是開始調度的時間已經錯過了
if (startTime > currentTime) {
// This is a delayed task.
//將延期的callback插入到延期隊列中
insertDelayedTask(newTask, startTime);
//若是調度隊列的頭任務沒有,而且延遲調度隊列的頭任務正好是新任務,
//說明全部任務均延期,而且此時的任務是第一個延期任務
if (firstTask === null && firstDelayedTask === newTask) {
// All tasks are delayed, and this is the task with the earliest delay.
//若是延遲調度開始的flag爲true,則取消定時的時間
if (isHostTimeoutScheduled) {
// Cancel an existing timeout.
cancelHostTimeout();
}
//不然設爲true
else {
isHostTimeoutScheduled = true;
}
// Schedule a timeout.
requestHostTimeout(handleTimeout, startTime - currentTime);
}
}
//沒有延期的話,則按計劃插入task
else {
insertScheduledTask(newTask, expirationTime);
// Schedule a host callback, if needed. If we're already performing work,
// wait until the next time we yield.
//更新調度執行的標誌
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
}
//返回通過包裝處理的task
return newTask;
}
複製代碼
當有新的update時,React 默認是 5s 後進行更新(直觀地說,就是你更新了開發代碼,過 5s,也能夠說是最遲過了 5s,網頁更新)
Scheduler_scheduleCallback()
的做用是:
① 肯定當前時間startTime
和延遲更新時間timeout
② 新建newTask
對象(包含callback
、expirationTime
)
③ 若是是延遲調度的話,將newTask
放入【延遲調度隊列】
④ 若是是正常調度的話,將newTask
放入【正常調度隊列】
⑤ 返回包裝的newTask
(2)當同步隊列不爲空
將該callback
入隊
scheduleSyncCallback()
最終返回臨時回調節點。
11、scheduleCallback()
做用:
若是是異步任務的話,則執行scheduleCallback()
,對callback
進行包裝處理,並更新調度隊列的狀態
源碼:
//對callback進行包裝處理,並更新調度隊列的狀態
export function scheduleCallback(
reactPriorityLevel: ReactPriorityLevel,
callback: SchedulerCallback,
options: SchedulerCallbackOptions | void | null,
) {
//獲取調度的優先級
const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
return Scheduler_scheduleCallback(priorityLevel, callback, options);
}
複製代碼
解析:
和十
同樣,調用Scheduler_scheduleCallback()
,將該callback
任務入隊,並把該callback
包裝成newTask
,賦給root.callbackNode
tips:func.bind(xx)
的意思是func
裏的this
綁定的是xx
,
也就是說 是xx
調用func
方法
注意!func.bind(xx)
這僅僅是綁定,而不是調用!
func.bind(xx)()
這樣纔算是xx
調用func
方法!
至此,scheduleCallbackForRoot()
已分析完畢(八到十一)
咱們講到這裏了:
export function scheduleUpdateOnFiber(){
xxx
xxx
xxx
if (expirationTime === Sync) {
if( 第一次render ){
xxx
}else{
/*八到十一講解的內容*/
scheduleCallbackForRoot(root, ImmediatePriority, Sync);
//當前沒有update時
if (executionContext === NoContext) {
//刷新同步任務隊列
flushSyncCallbackQueue();
}
}
}
}
複製代碼
12、flushSyncCallbackQueue()
做用:
更新同步任務隊列的狀態
源碼:
//刷新同步任務隊列
export function flushSyncCallbackQueue() {
//若是即時節點存在則中斷當前節點任務,從鏈表中移除task節點
if (immediateQueueCallbackNode !== null) {
Scheduler_cancelCallback(immediateQueueCallbackNode);
}
//更新同步隊列
flushSyncCallbackQueueImpl();
}
複製代碼
flushSyncCallbackQueueImpl():
//更新同步隊列
function flushSyncCallbackQueueImpl() {
//若是同步隊列未更新過而且同步隊列不爲空
if (!isFlushingSyncQueue && syncQueue !== null) {
// Prevent re-entrancy.
//防止重複執行,至關於一把鎖
isFlushingSyncQueue = true;
let i = 0;
try {
const isSync = true;
const queue = syncQueue;
//遍歷同步隊列,並更新刷新的狀態isSync=true
runWithPriority(ImmediatePriority, () => {
for (; i < queue.length; i++) {
let callback = queue[i];
do {
callback = callback(isSync);
} while (callback !== null);
}
});
//遍歷結束後置爲null
syncQueue = null;
} catch (error) {
// If something throws, leave the remaining callbacks on the queue.
if (syncQueue !== null) {
syncQueue = syncQueue.slice(i + 1);
}
// Resume flushing in the next tick
Scheduler_scheduleCallback(
Scheduler_ImmediatePriority,
flushSyncCallbackQueue,
);
throw error;
} finally {
isFlushingSyncQueue = false;
}
}
}
複製代碼
解析:
當前調度的任務被中斷時,先從鏈表中「移除」當前節點,並調用flushSyncCallbackQueueImpl ()
任務更新同步隊列
循環遍歷syncQueue
,並更新節點的isSync
狀態(isSync=true)
而後到這裏:
export function scheduleUpdateOnFiber(){
xxx
xxx
xxx
if (expirationTime === Sync) {
if( 第一次render ){
xxx
}else{
/*八到十一講解的內容*/
scheduleCallbackForRoot(root, ImmediatePriority, Sync);
if (executionContext === NoContext) {
//十二講的內容
flushSyncCallbackQueue();
}
}
}
//若是是異步任務的話,則當即執行調度任務
//對應if (expirationTime === Sync)
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);
}
}
}
}
複製代碼
scheduleUpdateOnFiber()的最後這一段沒看懂是什麼意思,猜想是調度結束以前,更新離散時間。
十3、scheduleWork流程圖
GitHub:
github.com/AttackXiaoJ…
(完)