schedule調(diào)度的整體流程
- React Fiber Scheduler 是 react16 最核心的一部分,這塊在
react-reconciler
這個包中 - 這個包的核心是 fiber reconciler,也即是 fiber 結(jié)構(gòu)
- fiber 的結(jié)構(gòu)幫助我們把react整個樹的應(yīng)用,更新的流程,能夠拆成每一個 fiber 對象為單元的一個更新的流程
- 這種單元的形式把更新拆分出來之后,給每個不同的任務(wù)提供一個優(yōu)先級,以及我們在更新的過程當(dāng)中,可以中斷
- 因為我們記錄更新到了哪一個單元,中斷了之后,可以過一會兒再回過頭來,繼續(xù)從這個單元開始,繼續(xù)之前沒有做完的更新
- 在 react 16之前 setState 產(chǎn)生的更新,必須從頭到尾更新完成,然后再執(zhí)行之后的代碼
- 如果我們的整個應(yīng)用樹,它的節(jié)點非常多,整個更新會導(dǎo)致它占用的js的運行時間會非常的多
- 讓頁面的其他的一些操作進入一個停滯的狀態(tài)
- 比如說,動畫的刷新或者是我們在 input 里面輸入內(nèi)容的時候,可能產(chǎn)生卡頓的感覺
- 所以,react 16之后,它的整體的更新流程是完全不一樣的
- 因為加入了中斷,掛起,這樣的功能,導(dǎo)致它的整個更新流程的調(diào)度變得非常的復(fù)雜
- 整個源碼體系,它每一個細節(jié),每一個變量的具體作用,都是值得琢磨的
- 理解它出于什么目的這么去設(shè)計,這時候才能深入整個react的更新體系中,這樣才能慢慢理解
全局變量一覽
- 調(diào)度過程中的全局變量,基本上都在 react-reconciler/src/ReactFiberScheduler.js 這個js里面
- https://github.com/facebook/react/blob/v16.6.3/packages/react-reconciler/src/ReactFiberScheduler.js
- 這個js的代碼是非常多的,它總共有兩千五百行代碼,而且注釋不算多
- 在這個文件里面,會存在著非常多的公共變量,就是說我們定義在這個文件的頂層的變量名
- 就是說我們定義在這個文件頂層作用域上面的很多的變量,在很多方法里面,它們是被共享的
- 這些公共變量對于去理解整個 schedule,它是非常重要的,因為它在很多方法里面都會有調(diào)用
- 它什么時候調(diào)用,什么時候被修改成什么值,用來記錄什么內(nèi)容
- 對于這些公共變量的理解,一方面來說比較的困難
- 另外一方面來說,它非常的重要
- 如果不能理解這些公共變量的作用,會導(dǎo)致看源碼的時候,看到一些地方會變得毫無頭緒
幾個重點需要關(guān)注的變量
// Used to ensure computeUniqueAsyncExpiration is monotonically decreasing.
let lastUniqueAsyncExpiration: number = Sync - 1;
let isWorking: boolean = false;
// The next work in progress fiber that we're currently working on.
let nextUnitOfWork: Fiber | null = null;
let nextRoot: FiberRoot | null = null;
// The time at which we're currently rendering work.
let nextRenderExpirationTime: ExpirationTime = NoWork;
let nextLatestAbsoluteTimeoutMs: number = -1;
let nextRenderDidError: boolean = false;
// The next fiber with an effect that we're currently committing.
let nextEffect: Fiber | null = null;
let isCommitting: boolean = false;
let rootWithPendingPassiveEffects: FiberRoot | null = null;
let passiveEffectCallbackHandle: * = null;
let passiveEffectCallback: * = null;
let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
// Used for performance tracking.
let interruptedBy: Fiber | null = null;
let stashedWorkInProgressProperties;
let replayUnitOfWork;
let mayReplayFailedUnitOfWork;
let isReplayingFailedUnitOfWork;
let originalReplayError;
let rethrowOriginalError;
-
isWorking
- commitRoot和renderRoot開始都會設(shè)置為true,然后在他們各自階段結(jié)束的時候都重置為false
- 用來標(biāo)志是否當(dāng)前有更新正在進行,不區(qū)分階段
-
isCommitting
- commitRoot開頭設(shè)置為true,結(jié)束之后設(shè)置為false
- 用來標(biāo)志是否處于commit階段
-
nextUnitOfWork
- 用于記錄render階段Fiber樹遍歷過程中下一個需要執(zhí)行的節(jié)點。
- 在resetStack中分別被重置
- 它只會指向workInProgress
-
nextRoot & nextRenderExpirationTime
- 用于記錄下一個將要渲染的root節(jié)點和下一個要渲染的任務(wù)的
-
nextEffect
- 用于commit階段記錄firstEffect -> lastEffect鏈遍歷過程中的每一個Fiber
下面是其他的一些全局變量
// Linked-list of roots
let firstScheduledRoot: FiberRoot | null = null;
let lastScheduledRoot: FiberRoot | null = null;
let callbackExpirationTime: ExpirationTime = NoWork;
let callbackID: *;
let isRendering: boolean = false;
let nextFlushedRoot: FiberRoot | null = null;
let nextFlushedExpirationTime: ExpirationTime = NoWork;
let lowestPriorityPendingInteractiveExpirationTime: ExpirationTime = NoWork;
let hasUnhandledError: boolean = false;
let unhandledError: mixed | null = null;
let isBatchingUpdates: boolean = false;
let isUnbatchingUpdates: boolean = false;
let completedBatches: Array<Batch> | null = null;
let originalStartTimeMs: number = now();
let currentRendererTime: ExpirationTime = msToExpirationTime(
originalStartTimeMs,
);
let currentSchedulerTime: ExpirationTime = currentRendererTime;
// Use these to prevent an infinite loop of nested updates
const NESTED_UPDATE_LIMIT = 50;
let nestedUpdateCount: number = 0;
let lastCommittedRootDuringThisBatch: FiberRoot | null = null;
-
firstScheduledRoot & lastScheduledRoot
- 用于存放有任務(wù)的所有root的單鏈表結(jié)構(gòu)
- 在findHighestPriorityRoot用來檢索優(yōu)先級最高的root
- 在addRootToSchedule中會修改
-
callbackExpirationTime & callbackID
- 記錄請求ReactScheduler的時候用的過期時間,如果在一次調(diào)度期間有新的調(diào)度請求進來了
- 而且優(yōu)先級更高,那么需要取消上一次請求,如果更低則無需再次請求調(diào)度。
- callbackID是ReactScheduler返回的用于取消調(diào)度的 ID
-
isRendering
- performWorkOnRoot開始設(shè)置為true,結(jié)束的時候設(shè)置為false
- 表示進入渲染階段,這是包含render和commit階段的
-
nextFlushedRoot & nextFlushedExpirationTime
- 用來標(biāo)志下一個需要渲染的root和對應(yīng)的expirtaionTime
-
deadline & deadlineDidExpire
- deadline是ReactScheduler中返回的時間片調(diào)度信息對象
- 用于記錄是否時間片調(diào)度是否過期,在shouldYield根據(jù)deadline是否過期來設(shè)置
-
isBatchingUpdates & isUnbatchingUpdates & isBatchingInteractiveUpdates
- batchedUpdates、unBatchedUpdates,deferredUpdates、interactiveUpdates等這些方法用來存儲更新產(chǎn)生的上下文的變量
-
originalStartTimeMs
- 固定值,js 加載完一開始計算的時間戳
-
currentRendererTime & currentSchedulerTime
- 計算從頁面加載到現(xiàn)在為止的毫秒數(shù),后者會在isRendering === true的時候
- 用作固定值返回,不然每次requestCurrentTime都會重新計算新的時間。
-
以上,每一個全局變量給它拿出來,單獨解釋它是在什么地方被用到
-
以及它是用來記錄哪些東西,是在什么情況下,才會發(fā)揮了哪些作用
調(diào)度流程
1 )第一階段

- 在調(diào)用
ReactDOM.render
,setState
,forceUpdate
,都會產(chǎn)生一個update - 產(chǎn)生update之后,進入 scheduleWork 進行調(diào)度
- scheduleWork 第一步操作是
addRootToScheduler
- 在一個rect應(yīng)用當(dāng)中,它不僅僅可能只存在一個 root 節(jié)點
- 因為我們通過
ReactDOM.render
調(diào)用的時候,就會創(chuàng)建一個 root 節(jié)點 - 如果調(diào)用多次
ReactDOM.render
,就可以創(chuàng)建多個 root 節(jié)點 - 這個時候, 整個應(yīng)用中就會存在著多個react的節(jié)點
- 在這些節(jié)點,可以單獨在內(nèi)部進行 setState,進行調(diào)度
- 它們都會有獨立的 updateQueen,有獨立的一個 fiber tree 來進行應(yīng)用的更新
- 一個應(yīng)用當(dāng)中可能會存在多個root, 所以這個時候就要去維護一下
- 因為,在同一時間可能有多個root會有更新存在, 所以有這么一個地方去維護它
- 這就是
addRootToScheduler
的一個作用
- scheduleWork 第一步操作是
-
addRootToScheduler
加入之后, 要先判斷一下是否正在 render 階段 或者 前后的root不同- 如果是,則調(diào)用
requestWork
,就要開始進行工作了,如果不是,我們就要return
- 因為之前的任務(wù)可能正在做,或者處于目前這個階段,不需要主動的再去調(diào)用一個
requestWork
來更新了
- 如果是,則調(diào)用
- 關(guān)于
requestWork
它里面做了什么?- 它判斷
expirationTime
,它是否是Sync
- 計算
expirationTime
調(diào)用的是computeExpirationForFiber
- 這時候會根據(jù) fiber 是否有
ConcurrentMode
的特性來計算 Sync 的 expirationTime 或者是異步的 expirationTime - 這個時候它最終會導(dǎo)致整體的一個更新模式的不同
- 因為如果是 Sync 的模式代表著我們這個更新要立馬進行執(zhí)行,要立馬更新到最終的 dom tree 上面
- 所以我們調(diào)用的是
performSyncWork
- 而如果它是一個 Async 模式的,那么說明它的優(yōu)先級不是特別高,那么他會進入一個調(diào)度的流程,因為它可以不立即更新
- 它本身的期望是在 expirationTime 結(jié)束之前,能夠被更新完成就可以了, 所以它的優(yōu)先級非常低,會進入到一整個調(diào)度的流程,即
scheduleCallbackWithExpirationTime
- 它判斷
2 )下一階段文章來源:http://www.zghlxwxcb.cn/news/detail-803032.html

- 整個調(diào)度的流程 react 給它單獨區(qū)分了一個包
packages/scheduler
- 用藍色的框給它圈起來,叫做 async schedule work
- 這一部分就涉及到整個異步的調(diào)度的過程
- 它利用的是瀏覽器當(dāng)中一個較新的API叫做
requestIdleCallback
, 能夠讓瀏覽器優(yōu)先進行他自己的任務(wù) - 比如說更新動畫,在每一幀有多余的時間的時候,它調(diào)用react給他設(shè)置了一個callback
- 然后就可以去執(zhí)行react一個更新,然后react會自己去記一個時
- 在這個時間內(nèi),我可以執(zhí)行我自己的工作
- 如果這個時間內(nèi)我的工作沒有執(zhí)行完,我要把javascript的運行的主動權(quán)交還給瀏覽器
- 讓瀏覽器去執(zhí)行它新的一些動畫的更新之類的,來讓瀏覽器保證高優(yōu)先級的任務(wù)能夠被立即執(zhí)行
- 所以,這就是這個藍色這一片區(qū)域的一個作用
- 它利用的是瀏覽器當(dāng)中一個較新的API叫做
- 在這里面調(diào)用的一個方法叫做
scheduleDeferredCallback
, 然后會有一個 callbackList - 因為可能多次調(diào)用這個方法去把 callback 設(shè)置進去
- 然后在這里面,我們雖然想要使用
requestIdleCallback
這個API - 但是, 大部分瀏覽器還不支持, 瀏覽器的兼容性也不是特別好
- 所以在react里面,它實現(xiàn)了自己的一個模擬 requestIdleCallback 的一個方式
- 它通過
requestAnimationFrame
和js
的任務(wù)隊列的原理來進行了一個模擬
- 它通過
- 在調(diào)用
requestIdleCallback
之后,說明瀏覽器有空了,可以去執(zhí)行react的更新了- 這就是我們加入到這里面的異步的更新任務(wù),它的優(yōu)先級比較低,瀏覽器有空的時候,再來執(zhí)行
- 因為 react 的任務(wù)它是有一個 expirationTime 的
- 所以它這里要判斷一下我的任務(wù)有沒有超時
- 如果已經(jīng)超時了,要把所有加入callbackList隊列的超時任務(wù)都執(zhí)行掉
- 因為任務(wù)已經(jīng)超時了,所以必須要立刻完成
- 執(zhí)行到第一個非超時的任務(wù)之后,若還有時間,可以繼續(xù)執(zhí)行
- 如果沒有時間了,要把主動權(quán)交還給瀏覽器,讓瀏覽器來做其他一些任務(wù)
- 最終要執(zhí)行這個任務(wù),執(zhí)行的是什么?
- 調(diào)用一個
performAsyncWork
這個方法 - 它會執(zhí)行react的schedule里面的回調(diào)函數(shù)
- 在調(diào)用這個方法的時候,schedule 會傳給這個方法一個叫做deadline的一個對象,這個對象是用來判斷。
- 在進入
performAsyncWork
的時候,就進入到react的一個更新流程 - react的更新流程中,它去遍歷整一棵樹,會遍歷每棵樹的每個單元,然后對它進行一個更新的操作
- 每個單元更新完了之后,回過頭來通過這個deadline對象判斷一下,現(xiàn)在是否還有 js 運行的一個時間片
- 因為調(diào)度器每一次調(diào)用
performAsyncWork
的任務(wù),它是有一個時間限制的 - 比如說默認的情況下是22毫秒,在這個時間片內(nèi)你可以去執(zhí)行的操作
- 這就是這個 deadline 對象它的一個作用
- 調(diào)用一個
- 最終調(diào)用
performWork
這個方法 -
performWork
它調(diào)用的是沒有deadline的 -
performAsyncWork
它調(diào)用的是有deadline的 - 最終在 if deadline 這里匯集在一起
- 根據(jù)是否有 deadline 進入下個階段的循環(huán)
3 )第三個階段文章來源地址http://www.zghlxwxcb.cn/news/detail-803032.html

- 根據(jù)是否有 deadline 進入循環(huán)
- 這個循環(huán)是什么呢?
- 這個循環(huán)就是要遍歷整棵樹每一個 fiber 節(jié)點進行的更新操作
- 對于同步任務(wù),它會遍歷完整棵樹,然后把整個應(yīng)用更新就完了
- 因為它是同步的,跟以前的react用法是一樣的
- 對于異步的來講,如果符合條件
- 進入
performWorkOnRoot
, 它做的其實是一個更新的過程 - 然后
findHighestPriorityRoot
找到一個最高優(yōu)先級的節(jié)點之后 - 對這個節(jié)點進行一個更新
recomputeCurrentRendererTime
- 對于有 deadline 的情況,調(diào)用
performWorkOnRoot
進行更新任務(wù)之后 - 在
renderRoot
里面,它還會有一個循環(huán)去判斷 deadline - 最終要等這個
performWorkOnRoot
返回之后,才會繼續(xù)下面的操作 - 對于有 deadline 的情況,會重新請求一個時間,然后去判斷一下deadline是否已經(jīng)過期
- 如果已經(jīng)過期的話,會回過頭來到紅色區(qū)域再進行一個判斷
- 如果發(fā)現(xiàn) deadline 已經(jīng)過期的話,又會去繼續(xù)調(diào)用這個
scheduleCallbackWithExpirationTime
- 再次進行異步的回調(diào), 它這是一個遞歸的過程
- 因為之前第一階段加入了一個
addRootToScheduler
- 它就有一個隊列,在維護著所有的更新的情況
- 對于每一次更新,只能更新一個優(yōu)先級的任務(wù),以及一個root上的任務(wù)
- 上述紅色這塊區(qū)域,它就是一個循環(huán)的條件判斷
- 它每次更新一個節(jié)點上的一個優(yōu)先級任務(wù)
- 具體的操作在
performWorkOnRoot
里面 - 更新完之后, 它會去調(diào)用相對應(yīng)的方法
- 在這個root上對應(yīng)的優(yōu)先級任務(wù)更新完之后
- 它要找到下一個root上面的對應(yīng)優(yōu)先級的任務(wù),然后再次進入這個循環(huán)
- 所以這個就是deadline它的一個用處,它幫助我們?nèi)ヅ袛嗍欠駪?yīng)該跳出循環(huán)了
- 如果我們一直處于這個循環(huán),要把所有任務(wù)都更新完,那么可能占用的js運行時間會非常的長
- 導(dǎo)致可能的動畫停滯或用戶輸入卡頓。
- 在這個 deadline 超過了之后,這個循環(huán)直接跳出
- 然后再繼續(xù)跳回到這個
scheduleCallbackWithExpirationTime
- 再次進入一個調(diào)度,然后把js的執(zhí)行權(quán)交給瀏覽器,讓它先執(zhí)行動畫或者用戶輸入的響應(yīng)
- 等有空了,再回過頭來再去執(zhí)行這個任務(wù),然后又回到 紅色區(qū)域判斷這里
- 之前沒完成的任務(wù),再繼續(xù)這么一個循環(huán)
- 最終達到的目的是要把 root 里面的所有的節(jié)點上面的所有更新都執(zhí)行完為止
- 這就是整的一個循環(huán)的一個過程
- 進入
整體流程圖

總結(jié)
- 通過
ReactDOM.render
,setState
,forceUpdate
這幾個方法產(chǎn)生了更新 - 產(chǎn)生了更新之后,維護一個隊列去保存這些更新以及對應(yīng)的root節(jié)點
- 然后,根據(jù)它任務(wù)的優(yōu)先級來進行判斷,判斷是同步的更新還是異步的更新
- 對于異步的更新,如果有多個任務(wù),它會一直處于先執(zhí)行瀏覽器優(yōu)先級最高的更新
- 有空的時候回過頭來更新 react 樹
- 如果 root 上對應(yīng)的某一個優(yōu)先級的任務(wù)更新完了
- 那么先輸出到dom上,然后執(zhí)行下一個更新
- 在這個循環(huán)的過程當(dāng)中,根據(jù)這個調(diào)度器傳入的 deadline 對象
- 判斷是否有超時,如果超時,再回過頭去進行一個調(diào)度
- 先把執(zhí)行權(quán)給瀏覽器,讓它保證動畫的流暢運行
- 等它有空,再回過頭來繼續(xù)執(zhí)行的任務(wù)
- 這就是整個調(diào)度的核心原理
- 目的是
- 保證我們低優(yōu)先級的react更新,不會阻止瀏覽器的一些高要求的動畫更新
- 能夠保證瀏覽器的一些動畫能夠達到30幀以上這么一個情況
- 以上就是react整個的調(diào)度過程
到了這里,關(guān)于React16源碼: React中的schedule調(diào)度整體流程的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!