源碼基于react@16.13.1
Fiber 是一個工作單元唱歧,它的引入是react實現(xiàn)任務(wù)分片和時間分片的基礎(chǔ)。分片是為了在Reconciliation階段(純js計算粒竖,無DOM操作)一點一點地執(zhí)行任務(wù)颅崩,給瀏覽器喘息的機(jī)會,從而在體驗上給用戶更流暢的使用感受蕊苗。
任務(wù)分片
一個工作單元是什么沿后?可以從代碼中直觀地理解。
import React from 'react'
import ReactDOM from 'react-dom'
function App() {
return (
<div>
<h1>Title</h1>
<p>
<a href='#'>link</a>
</p>
</div>
)
}
ReactDOM.createRoot(document.getElementById('app')).render(<App />)
以上結(jié)構(gòu)朽砰,被react分解成了6個fiber尖滚,也就是6個工作單元,如下圖
其中null對應(yīng)著fiber的根節(jié)點瞧柔,雖然在視覺上是什么都看不見的漆弄,但它的確在內(nèi)存里。剩下的fiber節(jié)點都比較好理解造锅。
每當(dāng)更新發(fā)生時撼唾,react會從fiber的根節(jié)點開始,一個一個地循環(huán)遍歷所有的fiber备绽。
react 通過循環(huán)券坞,一個一個地對fiber執(zhí)行performUnitOfWork操作,以此實現(xiàn)了任務(wù)分片肺素。
時間分片
時間分片的邏輯藏在循環(huán)里恨锚。
//react-reconciler -> ReactFiberWorkLoop.js
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
workInProgress = performUnitOfWork(workInProgress);
}
}
performUnitOfWork可能返回null或者下一個需要被執(zhí)行的fiber,返回結(jié)果存在workInProgress中倍靡。workInProgress在react-reconciler模塊中是全局變量猴伶。
當(dāng)shouldYield返回true的時候,循環(huán)語句中斷塌西,一個時間分片就結(jié)束了他挎,瀏覽器將重獲控制權(quán)。
以下任意條件成立時捡需,shouldYield會返回true
- 時間片到期(默認(rèn)5ms)
- 更緊急任務(wù)插隊
react 通過中斷任務(wù)循環(huán)办桨,實現(xiàn)了時間分片。
任務(wù)恢復(fù)
循環(huán)中斷時站辉,下一個未被完成的任務(wù)已經(jīng)被保存到react-reconciler模塊的全局變量workInProgress中呢撞。下一次循環(huán)開始時就從workInProgress開始。
跳出循環(huán)之后饰剥,react還做了一件事殊霞,通過MessageChannel發(fā)起了一個postMessage事件。
以上都發(fā)生在瀏覽器重獲控制權(quán)之前汰蓉。
而監(jiān)聽這個事件的绷蹲,正是循環(huán)的發(fā)起者performWorkUntilDeadline。
// scheduler.development.js
const performWorkUntilDeadline = () => {
if (scheduledHostCallback !== null) {
const currentTime = getCurrentTime();
deadline = currentTime + yieldInterval;
const hasTimeRemaining = true;
try {
// 通過scheduledHostCallback發(fā)起workLoopConcurrent循環(huán)
const hasMoreWork = scheduledHostCallback(
hasTimeRemaining,
currentTime,
);
if (!hasMoreWork) {
isMessageLoopRunning = false;
scheduledHostCallback = null;
} else {
port.postMessage(null); // 發(fā)起postMessage事件
}
} catch (error) {
port.postMessage(null);
throw error;
}
} else {
isMessageLoopRunning = false;
}
needsPaint = false;
};
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
循環(huán)中斷之后react執(zhí)行 port.postMessage 發(fā)起了一個message事件顾孽,并且通過事件監(jiān)聽又恢復(fù)了循環(huán)祝钢。在循環(huán)中斷到事件響應(yīng)的間隙,瀏覽器重獲了控制權(quán)若厚,執(zhí)行必要的渲染工作(如果有的話)太颤。
以上特性,目前只在開啟concurrent模式時有效盹沈。默認(rèn)模式下只有任務(wù)分片龄章,但是是同步執(zhí)行的,所以不存在時間分片乞封。