React Fiber架構(gòu)淺析
大廠技術(shù) 堅持周更 精選好文
1.瀏覽器渲染
為了更好的理解 React Fiber, 我們先簡單了解下渲染器進(jìn)程的內(nèi)部工作原理。
參考資料:
從內(nèi)部了解現(xiàn)代瀏覽器(3)[1] 渲染樹構(gòu)建、布局及繪制[2]
1.1 渲染幀
幀 (frame): 動畫過程中,每一幅靜止的畫面叫做幀。
幀率 (frame per second): 即每秒鐘播放的靜止畫面的數(shù)量。
幀時長 (frame running time): 每一幅靜止的畫面的停留時間。
丟幀 (dropped frame): 當(dāng)某一幀時長高于平均幀時長。
一般來說瀏覽器刷新率在60Hz, 渲染一幀時長必須控制在16.67ms (1s / 60 = 16.67ms)。 如果渲染超過該時間, 對用戶視覺上來說,會出現(xiàn)卡頓現(xiàn)象,即丟幀 (dropped frame)。
1.2 幀生命周期

圖: 簡單描述幀生命周期
簡單描述一幀的生命周期:
1. 一幀開始。
2. 主線程:
- Event Handlers: UI交互輸入的事件回調(diào), 例如input、click、wheel等。
- RAF: 執(zhí)行requestAnimationFrame回調(diào)。
- DOM Tree: 解析HTML, 構(gòu)建DOM Tree, 當(dāng)JS對DOM有變更會重新觸發(fā)該流程。
- CSS Tree: 構(gòu)建CSS Tree。至此構(gòu)建出Render Tree。
- Layout: 所有元素的position、size信息。
- Paint: 像素填充, 例如顏色、文字、邊框等可視部分。
- Composite: 繪制的指令信息傳到合成線程中。
- RequestIdleCallback: 如果此時一幀還有空余時間, 則執(zhí)行該回調(diào)。
3. 合成線程:
- Raster: 合成線程將信息分塊, 并把每塊發(fā)送給光柵線程, 光柵線程創(chuàng)建位圖, 并通知GPU進(jìn)程刷新這一幀。
4. 一幀結(jié)束。
1.3 丟幀實驗
怎么就丟幀了呢?
對于流暢的動畫,如果一幀處理時間超過16ms,就能感到頁面的卡頓了。
Demo: https://linjiayu6.github.io/FE-RequestIdleCallback-demo/
Github: RequestIdleCallback 實驗[3]
當(dāng)用戶點(diǎn)擊任一按鍵 A,B,C,因為主線程執(zhí)行Event Handlers任務(wù),動畫因為瀏覽器不能及時處理下一幀,導(dǎo)致動畫出現(xiàn)卡頓的現(xiàn)象。
// 處理同步任務(wù),并占用主線程
const bindClick = id =>
element(id).addEventListener('click', Work.onSyncUnit)
// 綁定click事件
bindClick('btnA')
bindClick('btnB')
bindClick('btnC')
var Work = {
// 有1萬個任務(wù)
unit: 10000,
// 處理每個任務(wù)
onOneUnit: function () { for (var i = 0; i <= 500000; i++) {} },
// 同步處理: 一次處理完所有任務(wù)
onSyncUnit: function () {
let _u = 0
while (_u < Work.unit) {
Work.onOneUnit()
_u ++
}
}
}

1.4 解決丟幀
上述,我們發(fā)現(xiàn) JS運(yùn)算是占用渲染的時間的。
在連續(xù)動畫中,要做高耗時的操作,如何保證幀平穩(wěn)呢?
解決丟幀思考如下:
在一幀空閑時處理, 利用 RequestIdleCallback[4] 處理任務(wù)。
window.requestIdleCallback()方法將在瀏覽器的空閑時段內(nèi)調(diào)用的函數(shù)排隊。這使開發(fā)者能夠在主事件循環(huán)上執(zhí)行后臺和低優(yōu)先級工作,而不會影響延遲關(guān)鍵事件,如動畫和輸入響應(yīng)。函數(shù)一般會按先進(jìn)先調(diào)用的順序執(zhí)行,然而,如果回調(diào)函數(shù)指定了執(zhí)行超時時間timeout,則有可能為了在超時前執(zhí)行函數(shù)而打亂執(zhí)行順序。
對高耗時的任務(wù),進(jìn)行分步驟處理。

Web worker 貌似也可以解決上述問題,這里不做擴(kuò)展。 ...
這里我們利用 RequestIdleCallback[5] 做個實驗咩。
Demo: https://linjiayu6.github.io/FE-RequestIdleCallback-demo/
Github: RequestIdleCallback 實驗[6]
const bindClick = id =>
element(id).addEventListener('click', Work.onAsyncUnit)
// 綁定click事件
bindClick('btnA')
bindClick('btnB')
bindClick('btnC')
var Work = {
// 有1萬個任務(wù)
unit: 10000,
// 處理每個任務(wù)
onOneUnit: function () { for (var i = 0; i <= 500000; i++) {} },
// 異步處理
onAsyncUnit: function () {
// 空閑時間 1ms
const FREE_TIME = 1
let _u = 0
function cb(deadline) {
// 當(dāng)任務(wù)還沒有被處理完 & 一幀還有的空閑時間 > 1ms
while (_u < Work.unit && deadline.timeRemaining() > FREE_TIME) {
Work.onOneUnit()
_u ++
}
// 任務(wù)干完, 執(zhí)行回調(diào)
if (_u >= Work.unit) {
// 執(zhí)行回調(diào)
return
}
// 任務(wù)沒完成, 繼續(xù)等空閑執(zhí)行
window.requestIdleCallback(cb)
}
window.requestIdleCallback(cb)
}
}

requestIdleCallback 啟發(fā)
將一個大任務(wù)分割成N個小任務(wù),在每一幀有空余時間情況下,逐步去執(zhí)行小任務(wù)。
2.React15 (-) 架構(gòu)缺點(diǎn)
React: stack reconciler實現(xiàn)[7]
React 算法之深度優(yōu)先遍歷[8]
遞歸 Recursion: 利用 調(diào)用棧[9],實現(xiàn)自己調(diào)用自己的方法。
最常見的就是 Leetcode: 斐波拉契數(shù)列[10] 、Leetcode: 70. 爬樓梯[11]。
2.1 概述原因

該情況,類似我們上述# 1.3丟幀實驗。
2.2 流程和代碼解析
可能需要你有點(diǎn) 深度優(yōu)先遍歷、遞歸、回溯思想、?? 等數(shù)據(jù)結(jié)構(gòu)的知識。
這里只做流程解析,代碼也為閹割版,重點(diǎn)是理解思想哈。
某React節(jié)點(diǎn)如下:
class A extends React.Component {
...
render() {
return (
<div id="app">
<h1></h1>
<p><h2></h2></p>
<h3></h3>
</div>
)
}
}

圖 DFS + 遞歸遍歷的路徑
下面是 ReactFiberWorkLoop.old.js[12] 閹割版代碼,為了簡要說明該流程。
// 工作循環(huán)同步處理
function workLoopSync() {
// 有任務(wù)
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork: Fiber): void {
// 對該節(jié)點(diǎn) 開始工作: return workInProgress.child; 返回的是該節(jié)點(diǎn)的孩子
let next = beginWork(...);
if (next === null) {
// 對某Node 完成工作: 回溯向上, 向上找到某節(jié)點(diǎn)的兄弟 sibling 或 直到向上為root代表, 遍歷結(jié)束。
completeUnitOfWork(unitOfWork);
} else {
// 從ta 孩子入手, 繼續(xù)向下工作
workInProgress = next;
}
}
/**
* siblingFiber: 兄弟節(jié)點(diǎn)
* returnFiber: 父親節(jié)點(diǎn)
*/
function completeUnitOfWork(unitOfWork: Fiber): void {
let completedWork = unitOfWork;
// 這里又是一個循環(huán)
do {
// 1. 判斷任務(wù)是否完成, 完成就打個完成的標(biāo)簽, 沒有完成就拋出異常
// 2. 如果有兄弟節(jié)點(diǎn), 那么接下來工作節(jié)點(diǎn)是該 xd
if (completedWork.sibling !== null) {
workInProgress = siblingFiber;
return;
}
// 3. 否則, 返回父親節(jié)點(diǎn)
completedWork = completedWork.return;
workInProgress = completedWork;
} while (completedWork !== null);
// 最后, 是root節(jié)點(diǎn), 結(jié)束
if (workInProgressRootExitStatus === RootIncomplete) {
workInProgressRootExitStatus = RootCompleted;
}
}
3.上述總結(jié)
因果關(guān)系
基于這些原因,React不得不重構(gòu)整個框架。
1. React (15ver-) 對創(chuàng)建和更新節(jié)點(diǎn)的處理,是通過 遞歸 ??。
2. 遞歸 , 在未完成對整個?? 的遍歷前,是不會停止的。
3. 該 任務(wù) 一直占用瀏覽器主線程,導(dǎo)致無 響應(yīng)優(yōu)先級更高 的任務(wù)。
4. 故,瀏覽器渲染超過臨界時間,從視覺上來看,卡死 ??。
主動思考
為了快速響應(yīng),防止丟幀,解決思路:
1. 將 任務(wù) 分解成 N個小任務(wù);
2. If 一幀里沒有 優(yōu)先級更高的任務(wù),則執(zhí)行自己。
else 有其他 優(yōu)先級高的事務(wù), 優(yōu)先執(zhí)行其他。
If 等一幀有 空閑 再執(zhí)行自己。
else 下一幀。
我們再回頭看下這個圖,問題即轉(zhuǎn)換如下:
如何將任務(wù)拆分?
如何判斷優(yōu)先級?
如何判斷一幀空閑時,再執(zhí)行?
...

Fiber 架構(gòu)
推薦 ?? https://github.com/7kms/react-illustration-series/tree/v17.0.1
推薦 ?? https://react.iamkasong.com/preparation/oldConstructure.html
下面,不會有大段大段代碼,去講具體的實現(xiàn)。
而是,以因果邏輯,帶你去了解 why,how,when (為什么、怎么做、何時做)。
4.抽象問題
上面我們說到了什么任務(wù)、優(yōu)先級等等,我們通過圖的方式,抽象下問題。

描述:
1. 任務(wù)A進(jìn)入執(zhí)行區(qū)域。
2. 在執(zhí)行任務(wù)A的過程中,更高優(yōu)先級任務(wù)B,請求被執(zhí)行。
3. 但因為先來后到嘛,此時任務(wù)B因為無法被執(zhí)行,而暫時被掛起,只能等待執(zhí)行。
4. 只有執(zhí)行完任務(wù)A后,才會執(zhí)行任務(wù)B。
上述流程可類比: 你在吃飯,突然你老板 給你打電話,你一定要堅持吃完飯,才接你老板的電話。
(腦補(bǔ)一下老板的表情??)
很明顯,這樣處理問題,效率奇低無比。
按照我們在前情總結(jié)部分的訴求,將上述圖變成這樣是不是更合理些。

描述:
1. 任務(wù)A進(jìn)入執(zhí)行區(qū)域。
2. 在執(zhí)行任務(wù)A的過程中,更高優(yōu)先級任務(wù)B,請求被執(zhí)行。
3. 考慮到任務(wù)B優(yōu)先級更高,則將任務(wù)A沒有執(zhí)行完成的部分,Stash暫存。
4. 任務(wù)B被執(zhí)行。當(dāng)任務(wù)B被執(zhí)行完成后,去執(zhí)行剩余沒有完成的任務(wù)A。
上述流程可類比: 你在吃飯,突然你老板給你打電話,即使你沒有吃完飯,也接起了你老板的電話,后繼續(xù)吃飯。(腦補(bǔ)一下老板的表情??)
5.核心關(guān)注
5.1 并發(fā)、調(diào)度

Concurrency & Scheduler
Concurrency 并發(fā): 有能力優(yōu)先處理更高優(yōu)事務(wù),同時對正在執(zhí)行的中途任務(wù)可暫存,待高優(yōu)完成后,再去執(zhí)行。
concurrency is the ability of different parts or units of a program[13], algorithm[14], or problem[15] to be [executed](https://en.wikipedia.org/wiki/Execution_(computing "executed")) out-of-order or at the same time simultaneously partial order[16], without affecting the final outcome.
https://en.wikipedia.org/wiki/Concurrency_(computer_science)
Scheduler 協(xié)調(diào)調(diào)度: 暫存未執(zhí)行任務(wù),等待時機(jī)成熟后,再去安排執(zhí)行剩下未完成任務(wù)。
考慮 所有任務(wù)可以被并發(fā)執(zhí)行,就需要有個協(xié)調(diào)任務(wù)的調(diào)度算法。
看到這里,不知道你有沒有發(fā)現(xiàn)一個大bug。
肯定是Call Stack[17]。
5.2 調(diào)用棧、虛擬調(diào)用棧幀

調(diào)用棧這里看起來就很不合理。
因為瀏覽器是利用調(diào)用棧來管理函數(shù)執(zhí)行順序的,秉承著先進(jìn)后出原則,是如何做到某任務(wù)都入棧了,但是因為中途有其他事兒,就被中斷。中斷就不算了,還能中斷后,接著后續(xù)再執(zhí)行。
問題突然間就變成: pause a functioin call (暫停對一個函數(shù)的調(diào)用)。
巧了,像 generator 和 瀏覽器debugger 就可以做到中斷函數(shù)調(diào)用。但考慮到可中斷渲染,并可重回構(gòu)造。React自行實現(xiàn)了一套體系叫做 React fiber 架構(gòu)。
React Fiber 核心: 自行實現(xiàn) 虛擬棧幀。
That's the purpose of React Fiber. Fiber is reimplementation of the stack, specialized for React components. You can think of a single fiber as a virtual stack frame.
https://github.com/acdlite/react-fiber-architecture
看到這里,是不是覺得 React yyds。ps: 反正看不太懂的都是 yyds。
5.3 React 16 (+) 架構(gòu)

6.數(shù)據(jù)結(jié)構(gòu)
FiberNode.js[18]
Fiber的數(shù)據(jù)結(jié)構(gòu)有三層信息: 實例屬性、構(gòu)建屬性、工作屬性。
下面以該demo代碼為例:
<div id="linjiayu">123</div>
<script type="text/babel">
const App = () => {
const [sum, onSetSum] = React.useState(0)
return (
<div id="app 1">
<h1 id="2-1 h1">標(biāo)題 h1</h1>
<ul id="2-2 ul">
<li id="3-1 li" onClick={() => onSetSum(d => d + 1)}>點(diǎn)擊 h2</li>
<li id="3-2 li">{sum}</li>
</ul>
<h3 id="2-3 h3">標(biāo)題 h3</h3>
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('linjiayu')
);
</script>
6.1 實例屬性
該Fiber的基本信息,例如組件類型等。

6.2 構(gòu)建屬性
構(gòu)建屬性 (return、child、sibling),根據(jù)上面代碼,我們構(gòu)建一個Fiber樹??。

構(gòu)建流程
和 2.2 流程和代碼解析 部分不同的是:
分為同步或異步更新。 且增加的異步更新 使用該字段 shouldYield 來判斷是否需要中斷。
// performSyncWorkOnRoot會調(diào)用該方法
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
// performConcurrentWorkOnRoot會調(diào)用該方法
function workLoopConcurrent() {
while (workInProgress !== null && ! shouldYield ()) {
performUnitOfWork(workInProgress);
}
}
在一個遞歸循環(huán)里,遞: beginWork()[19], 歸 completeWork()[20]
虛線: 表達(dá)構(gòu)建關(guān)系,但未完成狀態(tài)。
實線: 已構(gòu)建關(guān)系,并已執(zhí)行某個狀態(tài)。
實線 child 和 sibling 已執(zhí)行beginWork() 實線 return 已執(zhí)行 completeUnitOfWork()

1. 創(chuàng)建fiberNode FiberRootNode
2. 創(chuàng)建fiberNode rootFiber (即示例中 <div id="linjiayu">)
進(jìn)入循環(huán)工作區(qū)域, workInProgress(工作指針指向 rootFiber)
3. 創(chuàng)建fiberNode App
beginWork() -> 只有一個子節(jié)點(diǎn) -> workInProgress(工作指針指向App)
4. 創(chuàng)建fiberNode div
beginWork() -> 有多個子節(jié)點(diǎn) -> workInProgress(工作指針指向div)

5. 構(gòu)建孩子們節(jié)點(diǎn)
按照5.1 -> 5.2 -> 5.3 順序?qū)⒚總€節(jié)點(diǎn)創(chuàng)建。

6. workInProgress (工作指針指向h1)
beginWork() -> 沒有子節(jié)點(diǎn) -> completeUnitOfWork() -> 有兄弟節(jié)點(diǎn),繼續(xù) ...
6.3 工作屬性
【數(shù)據(jù)】數(shù)據(jù)的變更會導(dǎo)致UI層的變更。 【協(xié)調(diào)】為了減少對DOM的直接操作,通過Reconcile進(jìn)行diff查找,并將需要變更節(jié)點(diǎn),打上標(biāo)簽,變更路徑保留在effectList里。 【調(diào)度】待變更內(nèi)容要有Scheduler優(yōu)先級處理。
故,涉及到diff等查找操作,是需要有個高效手段來處理前后變化,即雙緩存機(jī)制。

有關(guān)雙緩存機(jī)制、數(shù)據(jù)更新、diff算法等,這里不做過多介紹。
7.Reconciler 和 Scheduler
上面,我們概述了fiberNode的數(shù)據(jù)結(jié)構(gòu),鏈表結(jié)構(gòu)即可支持隨時隨時中斷的訴求。
下面我們簡述下架構(gòu)中兩個核心模塊:
Reconciler (協(xié)調(diào)): 負(fù)責(zé)找出變化的組件。 Scheduler (調(diào)度): 負(fù)責(zé)找出高優(yōu)任務(wù)。
7.1 Reconciler 運(yùn)行流程淺析
【輸入】 當(dāng)數(shù)據(jù)初始化或變化,最后會調(diào)用 schedulerUpdateOnFiber該方法。
不需要調(diào)度,直接去構(gòu)造fiber樹。 需要調(diào)度,注冊調(diào)度任務(wù)。
// scheduleUpdateOnFiber(fiber, lane, eventTime) 以下為閹割版代碼
// 同步
if (lane === SyncLane) {
if (
// Check if we're inside unbatchedUpdates (沒有一次事件回調(diào)中觸發(fā)多次更新)
(executionContext & LegacyUnbatchedContext) !== NoContext &&
// Check if we're not already rendering (是否尚未渲染)
(executionContext & (RenderContext | CommitContext)) === NoContext) {
// 不調(diào)度, 直接去構(gòu)造fiber樹
performSyncWorkOnRoot(root);
}
}
// 否則,需要調(diào)度交給Scheduler后,再去構(gòu)造fiber樹
ensureRootIsScheduled(root, eventTime);
【注冊任務(wù)】 ensureRootIsScheduled
兩類任務(wù):
performSyncWorkOnRoot 同步構(gòu)建tree。 performConcurrentWorkOnRoot 異步構(gòu)建tree。
scheduleSyncCallback 或 scheduleCallback: 將上述兩類任務(wù)封裝到了對應(yīng)的任務(wù)隊列中。
// ensureRootIsScheduled
function ensureRootIsScheduled(root, currentTime) {
// ....
// 1. 優(yōu)先級最高,立刻馬上要同步執(zhí)行
if (newCallbackPriority === SyncLanePriority) {
newCallbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
// 2. 同步批量更新
} else if (newCallbackPriority === SyncBatchedLanePriority) {
newCallbackNode = scheduleCallback(ImmediatePriority$1, performSyncWorkOnRoot.bind(null, root));
} else {
// 3. 異步優(yōu)先級登記
var schedulerPriorityLevel = lanePriorityToSchedulerPriority(newCallbackPriority);
newCallbackNode = scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root));
}
// ...
// 更新rootFiber 任務(wù)
root.callbackNode = newCallbackNode;
}
同步任務(wù)會放到syncQueue 隊列,會被立即被執(zhí)行。
var _queue = syncQueue;
// 執(zhí)行所有同步任務(wù)
runWithPriority(ImmediatePriority, () => {
for (; i < queue.length; i++) {
let callback = queue[i];
do {
callback = callback(isSync);
} while (callback !== null);
}
});
// 清空同步任務(wù)
syncQueue = null;
異步處理會調(diào)用 scheduler方法 unstable_scheduleCallback,其實是requestIdleCallback替代品,該方法傳入回調(diào)任務(wù),和過期時間,來安排任務(wù)的執(zhí)行。
function unstable_scheduleCallback(callback, deprecated_options) {}
【執(zhí)行任務(wù)回調(diào)】
下面 performSyncWorkOnRoot 和 performConcurrentWorkOnRoot 不同的是: 異步執(zhí)行任務(wù),可隨時中斷渲染 shouldYield()
同步執(zhí)行構(gòu)建樹
function performSyncWorkOnRoot(root) {
// 1. 構(gòu)建樹
/*
renderRootSync 會 調(diào)用該方法 workLoopSync
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
*/
renderRootSync(root, lanes)
// 2. 輸出樹 (可看下雙緩存機(jī)制)
finishedWork = root.current.alternate;
}
異步執(zhí)行構(gòu)建樹
function performConcurrentWorkOnRoot(root) {
// 1. 構(gòu)建樹
/*
renderRootConcurrent 會 調(diào)用該方法 workLoopConcurrent
while (workInProgress !== null && !shouldYield() ) {
performUnitOfWork(workInProgress);
}
*/
renderRootConcurrent(root, lanes);
// 2. 輸出樹 (可看下雙緩存機(jī)制)
finishConcurrentRender(root, exitStatus, lanes);
// 3. check 是否還有其他更新, 是否需要發(fā)起新調(diào)度
ensureRootIsScheduled(root, now());
if (root.callbackNode === originalCallbackNode) {
// 當(dāng)前執(zhí)行的任務(wù)被中斷,返回個新的,再次渲染。
return performConcurrentWorkOnRoot.bind(null, root);
}
return null;
}
【輸出】
將變更內(nèi)容,輸出至界面。詳細(xì)看 commitRoot方法的實現(xiàn)。這里不做擴(kuò)展。
小總結(jié)

7.2 Scheduler 運(yùn)行流程淺析
workloop.js[21]
上面我們說到了同步和異步的任務(wù),異步任務(wù)是可以中斷且需要Scheduler配合處理。
注意只有異步任務(wù)即開啟了并發(fā)模式,才會有時間分片。
workLoop是 實現(xiàn)時間切片 和 可中斷渲染的核心。也是我們上面說到的虛擬棧幀的能力 。
以下為了說明,簡化流程:
// 并發(fā)任務(wù)的入口
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
// 有任務(wù) & 是否需要中斷
while (workInProgress !== null && !shouldYield() ) {
performUnitOfWork(workInProgress);
}
}
const scheduler = {
// 任務(wù)放到隊列里,等待空閑執(zhí)行
taskQueue: [
{
// 每個任務(wù)是個回調(diào)的概念, 且回調(diào)任務(wù)是可中斷的
callback: workLoopConcurrent
}
],
// 判斷: 是否需要中斷, 將控制權(quán)交給主進(jìn)程
shouldYieldToHost () {
// 沒有剩余時間
if (currentTime >= deadline) {
// 但需要渲染 和 有更高優(yōu)任務(wù)
if (needsPaint || scheduling.isInputPending()) {
return true; // 中斷
}
// 是否超過 300ms
return currentTime >= maxYieldInterval;
}
// 還有剩余時間
return false;
},
// 執(zhí)行入口可見
workLoop () {
// 當(dāng)前第一個任務(wù)
currentTask = taskQueue[0];
// 每次 currentTask 退出 就是一個時間切切片
while(currentTask !== null) {
// 任務(wù)沒有過期, 但一幀已經(jīng)無可用時間 或 需要被中斷, 則讓出主線程
// 每一次執(zhí)行均進(jìn)行超時檢測,做到讓出主線程。
if (currentTask.expirationTime > currentTime
&& (!hasTimeRemaining || shouldYieldToHost())) {
break
}
// 執(zhí)行任務(wù)
const callback = currentTask.callback;
const continuationCallback = callback(didUserCallbackTimeout);
// 如果該任務(wù)后, 還有連續(xù)回調(diào)
if (typeof continuationCallback === 'function') {
// 則保留當(dāng)前
currentTask.callback = continuationCallback;
} else {
// 將currentTask移除該隊列
pop(taskQueue);
}
// 更新currentTask
currentTask = peek(taskQueue);
}
},
}
簡而言之:
有個任務(wù)隊列 queue,該隊列存放可中斷的任務(wù)。
workLoop對隊列里取第一個任務(wù)currentTask,進(jìn)入循環(huán)開始執(zhí)行。如果任務(wù)執(zhí)行完后,還有連續(xù)的回調(diào),則 currentTask.callback = continuationCallback 否則移除已完成的任務(wù) 當(dāng)該任務(wù)沒有時間 或 需要中斷 (渲染任務(wù) 或 其他高優(yōu)任務(wù)插入等),則讓出主線程。
否則執(zhí)行任務(wù) currentTask.callback()
更新任務(wù)currentTask,繼續(xù)循環(huán)走起。
這里還涉及更多細(xì)節(jié),例如:
requestAnimationFrame 計算一幀的空余時間; 使用new MessageChannel () 執(zhí)行宏任務(wù); 優(yōu)先級; ...
這里不做詳細(xì)說明。
8.小總結(jié)
我們想要實現(xiàn)并發(fā)訴求,就需要從底層重構(gòu),即FiberNode的實現(xiàn)。 調(diào)用棧call stack是無法做到并發(fā) (異步可中斷) 訴求,故React自行實現(xiàn)了一套虛擬棧幀。 虛擬棧幀 是要具備調(diào)度能力的,也就是如何在適當(dāng)?shù)臅r候去執(zhí)行任務(wù)。 scheduler 可做到異步可中斷,并可自主分配優(yōu)先級高低的任務(wù)。
(即任務(wù) (狀態(tài): 運(yùn)行/中斷/繼續(xù)) Lane運(yùn)行策略)
(實際上,scheduler + Lane 調(diào)度策略遠(yuǎn)比該處理復(fù)雜的多??)

圖: 前后對比 (個人理解, 錯誤請指正)
以上,同學(xué)們是不是對React Fiber架構(gòu)有了初步的理解哦~
其他說明
雙緩存機(jī)制
參考: 雙緩存Fiber樹[22]

至多有兩棵 Fiber Tree。
分別叫做current fiber tree 和 workInProgress fiber tree。
即在屏幕上已建立的fiber tree 和 因為數(shù)據(jù)變化重新在內(nèi)存里創(chuàng)建的fiber tree。
他們之間是通過 alternate屬性(指針) 建立連接。

簡單的說:
就是workInProgress fiber的創(chuàng)建 是否可復(fù)用 current fiber的節(jié)點(diǎn)。后續(xù)可再詳看diff算法。 workInProgress fiber tree 將確定要變更節(jié)點(diǎn),渲染到屏幕上。 workInProgress fiber tree 晉升為 current fiber tree。

參考資料
從內(nèi)部了解現(xiàn)代瀏覽器(3): https://juejin.cn/post/6844903687383416840
[2]渲染樹構(gòu)建、布局及繪制: https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction
[3]RequestIdleCallback 實驗: https://github.com/Linjiayu6/FE-RequestIdleCallback-demo
[4]RequestIdleCallback: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback
[5]RequestIdleCallback: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback
[6]RequestIdleCallback 實驗: https://github.com/Linjiayu6/FE-RequestIdleCallback-demo
[7]React: stack reconciler實現(xiàn): https://zh-hans.reactjs.org/docs/implementation-notes.html
[8]React 算法之深度優(yōu)先遍歷: https://juejin.cn/post/6912280245055782920
[9]調(diào)用棧: https://segmentfault.com/a/1190000010360316
[10]Leetcode: 斐波拉契數(shù)列: https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/
[11]Leetcode: 70. 爬樓梯: https://leetcode-cn.com/problems/climbing-stairs/
[12]ReactFiberWorkLoop.old.js: https://github.com/facebook/react/blob/v17.0.1/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1558
[13]program: https://en.wikipedia.org/wiki/Computer_program
[14]algorithm: https://en.wikipedia.org/wiki/Algorithm
[15]problem: https://en.wikipedia.org/wiki/Problem_solving
[16]partial order: https://en.wikipedia.org/wiki/Partial_Order
[17]Call Stack: https://segmentfault.com/a/1190000021456103
[18]FiberNode.js: https://github.com/facebook/react/blob/1fb18e22ae66fdb1dc127347e169e73948778e5a/packages/react-reconciler/src/ReactFiber.new.js#L117
[19]beginWork(): https://github.com/facebook/react/blob/970fa122d8188bafa600e9b5214833487fbf1092/packages/react-reconciler/src/ReactFiberBeginWork.new.js#L3058
[20]completeWork(): https://github.com/facebook/react/blob/970fa122d8188bafa600e9b5214833487fbf1092/packages/react-reconciler/src/ReactFiberCompleteWork.new.js#L652
[21]workloop.js: https://github.com/facebook/react/blob/v17.0.1/packages/scheduler/src/Scheduler.js#L164
[22]雙緩存Fiber樹: https://react.iamkasong.com/process/doubleBuffer.html#update%E6%97%B6
?? 謝謝支持
以上便是本次分享的全部內(nèi)容,希望對你有所幫助^_^
喜歡的話別忘了 分享、點(diǎn)贊、收藏 三連哦~。
歡迎關(guān)注公眾號 前端Sharing 收貨大廠一手好文章~
