React Fiber 架構淺析
- 瀏覽器渲染
爲了更好的理解 React Fiber, 我們先簡單瞭解下渲染器進程的內部工作原理。
參考資料:
從內部瞭解現代瀏覽器 (3)[1]
渲染樹構建、佈局及繪製 [2]
1.1 渲染幀
幀 (frame): 動畫過程中,每一幅靜止的畫面叫做幀。
幀率 (frame per second): 即每秒鐘播放的靜止畫面的數量。
幀時長 (frame running time): 每一幅靜止的畫面的停留時間。
丟幀 (dropped frame): 當某一幀時長高於平均幀時長。
-
一般來說瀏覽器刷新率在 60Hz, 渲染一幀時長必須控制在 16.67ms (1s / 60 = 16.67ms)。
-
如果渲染超過該時間, 對用戶視覺上來說,會出現卡頓現象,即丟幀 (dropped frame)。
1.2 幀生命週期
圖: 簡單描述幀生命週期
簡單描述一幀的生命週期:
1. 一幀開始。
2. 主線程:
- Event Handlers: UI交互輸入的事件回調, 例如input、click、wheel等。
- RAF: 執行requestAnimationFrame回調。
- DOM Tree: 解析HTML, 構建DOM Tree, 當JS對DOM有變更會重新觸發該流程。
- CSS Tree: 構建CSS Tree。至此構建出Render Tree。
- Layout: 所有元素的position、size信息。
- Paint: 像素填充, 例如顏色、文字、邊框等可視部分。
- Composite: 繪製的指令信息傳到合成線程中。
- RequestIdleCallback: 如果此時一幀還有空餘時間, 則執行該回調。
3. 合成線程:
- Raster: 合成線程將信息分塊, 並把每塊發送給光柵線程, 光柵線程創建位圖, 並通知GPU進程刷新這一幀。
4. 一幀結束。
1.3 丟幀實驗
怎麼就丟幀了呢?
對於流暢的動畫,如果一幀處理時間超過 16ms,就能感到頁面的卡頓了。
Demo: https://linjiayu6.github.io/FE-RequestIdleCallback-demo/
Github: RequestIdleCallback 實驗 [3]
當用戶點擊任一按鍵 A,B,C,因爲主線程執行 Event Handlers 任務,動畫因爲瀏覽器不能及時處理下一幀,導致動畫出現卡頓的現象。
// 處理同步任務,並佔用主線程
const bindClick = id =>
element(id).addEventListener('click', Work.onSyncUnit)
// 綁定click事件
bindClick('btnA')
bindClick('btnB')
bindClick('btnC')
var Work = {
// 有1萬個任務
unit: 10000,
// 處理每個任務
onOneUnit: function () { for (var i = 0; i <= 500000; i++) {} },
// 同步處理: 一次處理完所有任務
onSyncUnit: function () {
let _u = 0
while (_u < Work.unit) {
Work.onOneUnit()
_u ++
}
}
}
1.4 解決丟幀
上述,我們發現 JS 運算是佔用渲染的時間的。
在連續動畫中,要做高耗時的操作,如何保證幀平穩呢?
解決丟幀思考如下:
- 在一幀空閒時處理, 利用 RequestIdleCallback[4] 處理任務。
window.requestIdleCallback() 方法將在瀏覽器的空閒時段內調用的函數排隊。這使開發者能夠在主事件循環上執行後臺和低優先級工作,而不會影響延遲關鍵事件,如動畫和輸入響應。函數一般會按先進先調用的順序執行,然而,如果回調函數指定了執行超時時間 timeout,則有可能爲了在超時前執行函數而打亂執行順序。
- 對高耗時的任務,進行分步驟處理。
-
Web worker 貌似也可以解決上述問題,這裏不做擴展。
-
...
這裏我們利用 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萬個任務
unit: 10000,
// 處理每個任務
onOneUnit: function () { for (var i = 0; i <= 500000; i++) {} },
// 異步處理
onAsyncUnit: function () {
// 空閒時間 1ms
const FREE_TIME = 1
let _u = 0
function cb(deadline) {
// 當任務還沒有被處理完 & 一幀還有的空閒時間 > 1ms
while (_u < Work.unit && deadline.timeRemaining() > FREE_TIME) {
Work.onOneUnit()
_u ++
}
// 任務幹完, 執行回調
if (_u >= Work.unit) {
// 執行回調
return
}
// 任務沒完成, 繼續等空閒執行
window.requestIdleCallback(cb)
}
window.requestIdleCallback(cb)
}
}
requestIdleCallback 啓發
將一個大任務分割成 N 個小任務,在每一幀有空餘時間情況下,逐步去執行小任務。
2.React15 (-) 架構缺點
React: stack reconciler 實現 [7]
React 算法之深度優先遍歷 [8]
遞歸 Recursion: 利用 調用棧 [9],實現自己調用自己的方法。
最常見的就是 Leetcode: 斐波拉契數列 [10] 、Leetcode: 70. 爬樓梯 [11]。
2.1 概述原因
該情況,類似我們上述# 1.3 丟幀實驗。
2.2 流程和代碼解析
可能需要你有點 深度優先遍歷、遞歸、回溯思想、🌲 等數據結構的知識。
這裏只做流程解析,代碼也爲閹割版,重點是理解思想哈。
某 React 節點如下:
class A extends React.Component {
...
render() {
return (
<div id="app">
<h1></h1>
<p><h2></h2></p>
<h3></h3>
</div>
)
}
}
圖 DFS + 遞歸遍歷的路徑
下面是 ReactFiberWorkLoop.old.js[12] 閹割版代碼,爲了簡要說明該流程。
// 工作循環同步處理
function workLoopSync() {
// 有任務
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork: Fiber): void {
// 對該節點 開始工作: return workInProgress.child; 返回的是該節點的孩子
let next = beginWork(...);
if (next === null) {
// 對某Node 完成工作: 回溯向上, 向上找到某節點的兄弟 sibling 或 直到向上爲root代表, 遍歷結束。
completeUnitOfWork(unitOfWork);
} else {
// 從ta 孩子入手, 繼續向下工作
workInProgress = next;
}
}
/**
* siblingFiber: 兄弟節點
* returnFiber: 父親節點
*/
function completeUnitOfWork(unitOfWork: Fiber): void {
let completedWork = unitOfWork;
// 這裏又是一個循環
do {
// 1. 判斷任務是否完成, 完成就打個完成的標籤, 沒有完成就拋出異常
// 2. 如果有兄弟節點, 那麼接下來工作節點是該 xd
if (completedWork.sibling !== null) {
workInProgress = siblingFiber;
return;
}
// 3. 否則, 返回父親節點
completedWork = completedWork.return;
workInProgress = completedWork;
} while (completedWork !== null);
// 最後, 是root節點, 結束
if (workInProgressRootExitStatus === RootIncomplete) {
workInProgressRootExitStatus = RootCompleted;
}
}
- 上述總結
因果關係
基於這些原因,React 不得不重構整個框架。
1. React (15ver-) 對創建和更新節點的處理,是通過 遞歸 🌲。
2. 遞歸 , 在未完成對整個🌲 的遍歷前,是不會停止的。
3. 該 任務 一直佔用瀏覽器主線程,導致無 響應優先級更高 的任務。
4. 故,瀏覽器渲染超過臨界時間,從視覺上來看,卡死 🐶。
主動思考
爲了快速響應,防止丟幀,解決思路:
1. 將 任務 分解成 N個小任務;
2. If 一幀裏沒有 優先級更高的任務,則執行自己。
else 有其他 優先級高的事務, 優先執行其他。
If 等一幀有 空閒 再執行自己。
else 下一幀。
我們再回頭看下這個圖,問題即轉換如下:
如何將任務拆分?
如何判斷優先級?
如何判斷一幀空閒時,再執行?
...
Fiber 架構
推薦 👍 https://github.com/7kms/react-illustration-series/tree/v17.0.1
推薦 👍 https://react.iamkasong.com/preparation/oldConstructure.html
下面,不會有大段大段代碼,去講具體的實現。
而是,以因果邏輯,帶你去了解 why,how,when (爲什麼、怎麼做、何時做)。
- 抽象問題
上面我們說到了什麼任務、優先級等等,我們通過圖的方式,抽象下問題。
描述:
1. 任務A進入執行區域。
2. 在執行任務A的過程中,更高優先級任務B,請求被執行。
3. 但因爲先來後到嘛,此時任務B因爲無法被執行,而暫時被掛起,只能等待執行。
4. 只有執行完任務A後,纔會執行任務B。
上述流程可類比: 你在喫飯,突然你老闆 給你打電話,你一定要堅持喫完飯,才接你老闆的電話。
(腦補一下老闆的表情😭)
很明顯,這樣處理問題,效率奇低無比。
按照我們在前情總結部分的訴求,將上述圖變成這樣是不是更合理些。
描述:
1. 任務A進入執行區域。
2. 在執行任務A的過程中,更高優先級任務B,請求被執行。
3. 考慮到任務B優先級更高,則將任務A沒有執行完成的部分,Stash暫存。
4. 任務B被執行。當任務B被執行完成後,去執行剩餘沒有完成的任務A。
上述流程可類比: 你在喫飯,突然你老闆給你打電話,即使你沒有喫完飯,也接起了你老闆的電話,後繼續喫飯。(腦補一下老闆的表情😊)
- 核心關注
5.1 併發、調度
Concurrency & Scheduler
Concurrency 併發: 有能力優先處理更高優事務,同時對正在執行的中途任務可暫存,待高優完成後,再去執行。
concurrency is the ability of different parts or units of a program[13], algorithm[14], or problem[15] to be 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 協調調度: 暫存未執行任務,等待時機成熟後,再去安排執行剩下未完成任務。
考慮 所有任務可以被併發執行,就需要有個協調任務的調度算法。
看到這裏,不知道你有沒有發現一個大 bug。
肯定是 Call Stack[17]。
5.2 調用棧、虛擬調用棧幀
調用棧這裏看起來就很不合理。
因爲瀏覽器是利用調用棧來管理函數執行順序的,秉承着先進後出原則,是如何做到某任務都入棧了,但是因爲中途有其他事兒,就被中斷。中斷就不算了,還能中斷後,接着後續再執行。
問題突然間就變成: pause a functioin call (暫停對一個函數的調用)。
巧了,像 generator 和 瀏覽器 debugger 就可以做到中斷函數調用。但考慮到可中斷渲染,並可重回構造。React 自行實現了一套體系叫做 React fiber 架構。
React Fiber 核心: 自行實現 虛擬棧幀。
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 (+) 架構
- 數據結構
FiberNode.js[18]
Fiber 的數據結構有三層信息: 實例屬性、構建屬性、工作屬性。
下面以該 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">標題 h1</h1>
<ul id="2-2 ul">
<li id="3-1 li" onClick={() => onSetSum(d => d + 1)}>點擊 h2</li>
<li id="3-2 li">{sum}</li>
</ul>
<h3 id="2-3 h3">標題 h3</h3>
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('linjiayu')
);
</script>
6.1 實例屬性
該 Fiber 的基本信息,例如組件類型等。
6.2 構建屬性
構建屬性 (return、child、sibling),根據上面代碼,我們構建一個 Fiber 樹🌲。
構建流程
和 2.2 流程和代碼解析 部分不同的是:
-
分爲同步或異步更新。
-
且增加的異步更新 使用該字段 shouldYield 來判斷是否需要中斷。
// performSyncWorkOnRoot會調用該方法
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
// performConcurrentWorkOnRoot會調用該方法
function workLoopConcurrent() {
while (workInProgress !== null && ! shouldYield ()) {
performUnitOfWork(workInProgress);
}
}
在一個遞歸循環裏,遞: beginWork()[19], 歸 completeWork()[20]
虛線: 表達構建關係,但未完成狀態。
實線: 已構建關係,並已執行某個狀態。
實線 child 和 sibling 已執行 beginWork()
實線 return 已執行 completeUnitOfWork()
1. 創建fiberNode FiberRootNode
2. 創建fiberNode rootFiber (即示例中 <div id="linjiayu">)
進入循環工作區域, workInProgress(工作指針指向 rootFiber)
3. 創建fiberNode App
beginWork() -> 只有一個子節點 -> workInProgress(工作指針指向App)
4. 創建fiberNode div
beginWork() -> 有多個子節點 -> workInProgress(工作指針指向div)
5. 構建孩子們節點
按照5.1 -> 5.2 -> 5.3 順序將每個節點創建。
6. workInProgress (工作指針指向h1)
beginWork() -> 沒有子節點 -> completeUnitOfWork() -> 有兄弟節點,繼續 ...
6.3 工作屬性
-
【數據】數據的變更會導致 UI 層的變更。
-
【協調】爲了減少對 DOM 的直接操作,通過 Reconcile 進行 diff 查找,並將需要變更節點,打上標籤,變更路徑保留在 effectList 裏。
-
【調度】待變更內容要有 Scheduler 優先級處理。
故,涉及到 diff 等查找操作,是需要有個高效手段來處理前後變化,即雙緩存機制。
有關雙緩存機制、數據更新、diff 算法等,這裏不做過多介紹。
7.Reconciler 和 Scheduler
上面,我們概述了 fiberNode 的數據結構,鏈表結構即可支持隨時隨時中斷的訴求。
下面我們簡述下架構中兩個核心模塊:
-
Reconciler (協調): 負責找出變化的組件。
-
Scheduler (調度): 負責找出高優任務。
7.1 Reconciler 運行流程淺析
- 【輸入】 當數據初始化或變化,最後會調用
schedulerUpdateOnFiber
該方法。
-
不需要調度,直接去構造 fiber 樹。
-
需要調度,註冊調度任務。
// scheduleUpdateOnFiber(fiber, lane, eventTime) 以下爲閹割版代碼
// 同步
if (lane === SyncLane) {
if (
// Check if we're inside unbatchedUpdates (沒有一次事件回調中觸發多次更新)
(executionContext & LegacyUnbatchedContext) !== NoContext &&
// Check if we're not already rendering (是否尚未渲染)
(executionContext & (RenderContext | CommitContext)) === NoContext) {
// 不調度, 直接去構造fiber樹
performSyncWorkOnRoot(root);
}
}
// 否則,需要調度交給Scheduler後,再去構造fiber樹
ensureRootIsScheduled(root, eventTime);
- 【註冊任務】
ensureRootIsScheduled
兩類任務:
-
performSyncWorkOnRoot 同步構建 tree。
-
performConcurrentWorkOnRoot 異步構建 tree。
scheduleSyncCallback 或 scheduleCallback: 將上述兩類任務封裝到了對應的任務隊列中。
// ensureRootIsScheduled
function ensureRootIsScheduled(root, currentTime) {
// ....
// 1. 優先級最高,立刻馬上要同步執行
if (newCallbackPriority === SyncLanePriority) {
newCallbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
// 2. 同步批量更新
} else if (newCallbackPriority === SyncBatchedLanePriority) {
newCallbackNode = scheduleCallback(ImmediatePriority$1, performSyncWorkOnRoot.bind(null, root));
} else {
// 3. 異步優先級登記
var schedulerPriorityLevel = lanePriorityToSchedulerPriority(newCallbackPriority);
newCallbackNode = scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root));
}
// ...
// 更新rootFiber 任務
root.callbackNode = newCallbackNode;
}
同步任務會放到 syncQueue 隊列,會被立即被執行。
var _queue = syncQueue;
// 執行所有同步任務
runWithPriority(ImmediatePriority, () => {
for (; i < queue.length; i++) {
let callback = queue[i];
do {
callback = callback(isSync);
} while (callback !== null);
}
});
// 清空同步任務
syncQueue = null;
異步處理會調用 scheduler 方法 unstable_scheduleCallback
,其實是 requestIdleCallback 替代品,該方法傳入回調任務,和過期時間,來安排任務的執行。
function unstable_scheduleCallback(callback, deprecated_options) {}
- 【執行任務回調】
下面 performSyncWorkOnRoot
和 performConcurrentWorkOnRoot
不同的是: 異步執行任務,可隨時中斷渲染 shouldYield()
同步執行構建樹
function performSyncWorkOnRoot(root) {
// 1. 構建樹
/*
renderRootSync 會 調用該方法 workLoopSync
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
*/
renderRootSync(root, lanes)
// 2. 輸出樹 (可看下雙緩存機制)
finishedWork = root.current.alternate;
}
異步執行構建樹
function performConcurrentWorkOnRoot(root) {
// 1. 構建樹
/*
renderRootConcurrent 會 調用該方法 workLoopConcurrent
while (workInProgress !== null && !shouldYield() ) {
performUnitOfWork(workInProgress);
}
*/
renderRootConcurrent(root, lanes);
// 2. 輸出樹 (可看下雙緩存機制)
finishConcurrentRender(root, exitStatus, lanes);
// 3. check 是否還有其他更新, 是否需要發起新調度
ensureRootIsScheduled(root, now());
if (root.callbackNode === originalCallbackNode) {
// 當前執行的任務被中斷,返回個新的,再次渲染。
return performConcurrentWorkOnRoot.bind(null, root);
}
return null;
}
- 【輸出】
將變更內容,輸出至界面。詳細看 commitRoot
方法的實現。這裏不做擴展。
- 小總結
7.2 Scheduler 運行流程淺析
workloop.js[21]
上面我們說到了同步和異步的任務,異步任務是可以中斷且需要 Scheduler 配合處理。
注意只有異步任務即開啓了併發模式,纔會有時間分片。
workLoop 是 實現時間切片 和 可中斷渲染的核心。也是我們上面說到的虛擬棧幀的能力 。
以下爲了說明,簡化流程:
// 併發任務的入口
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
// 有任務 & 是否需要中斷
while (workInProgress !== null && !shouldYield() ) {
performUnitOfWork(workInProgress);
}
}
const scheduler = {
// 任務放到隊列裏,等待空閒執行
taskQueue: [
{
// 每個任務是個回調的概念, 且回調任務是可中斷的
callback: workLoopConcurrent
}
],
// 判斷: 是否需要中斷, 將控制權交給主進程
shouldYieldToHost () {
// 沒有剩餘時間
if (currentTime >= deadline) {
// 但需要渲染 和 有更高優任務
if (needsPaint || scheduling.isInputPending()) {
return true; // 中斷
}
// 是否超過 300ms
return currentTime >= maxYieldInterval;
}
// 還有剩餘時間
return false;
},
// 執行入口可見
workLoop () {
// 當前第一個任務
currentTask = taskQueue[0];
// 每次 currentTask 退出 就是一個時間切切片
while(currentTask !== null) {
// 任務沒有過期, 但一幀已經無可用時間 或 需要被中斷, 則讓出主線程
// 每一次執行均進行超時檢測,做到讓出主線程。
if (currentTask.expirationTime > currentTime
&& (!hasTimeRemaining || shouldYieldToHost())) {
break
}
// 執行任務
const callback = currentTask.callback;
const continuationCallback = callback(didUserCallbackTimeout);
// 如果該任務後, 還有連續回調
if (typeof continuationCallback === 'function') {
// 則保留當前
currentTask.callback = continuationCallback;
} else {
// 將currentTask移除該隊列
pop(taskQueue);
}
// 更新currentTask
currentTask = peek(taskQueue);
}
},
}
簡而言之:
-
有個任務隊列 queue,該隊列存放可中斷的任務。
-
workLoop
對隊列裏取第一個任務 currentTask,進入循環開始執行。
-
如果任務執行完後,還有連續的回調,則 currentTask.callback = continuationCallback
-
否則移除已完成的任務
-
當該任務沒有時間 或 需要中斷 (渲染任務 或 其他高優任務插入等),則讓出主線程。
-
否則執行任務 currentTask.callback()
-
更新任務 currentTask,繼續循環走起。
這裏還涉及更多細節,例如:
-
requestAnimationFrame 計算一幀的空餘時間;
-
使用 new MessageChannel () 執行宏任務;
-
優先級;
-
...
這裏不做詳細說明。
- 小總結
-
我們想要實現併發訴求,就需要從底層重構,即 FiberNode 的實現。
-
調用棧 call stack 是無法做到併發 (異步可中斷) 訴求,故 React 自行實現了一套虛擬棧幀。
-
虛擬棧幀 是要具備調度能力的,也就是如何在適當的時候去執行任務。
-
scheduler 可做到異步可中斷,並可自主分配優先級高低的任務。
(即任務 (狀態: 運行 / 中斷 / 繼續) Lane 運行策略)
(實際上,scheduler + Lane 調度策略遠比該處理複雜的多😭)
圖: 前後對比 (個人理解, 錯誤請指正)
以上,同學們是不是對 React Fiber 架構有了初步的理解哦~
其他說明
雙緩存機制
參考: 雙緩存 Fiber 樹 [22]
至多有兩棵 Fiber Tree。
分別叫做 current fiber tree 和 workInProgress fiber tree。
即在屏幕上已建立的 fiber tree 和 因爲數據變化重新在內存裏創建的 fiber tree。
他們之間是通過 alternate 屬性 (指針) 建立連接。
簡單的說:
-
就是 workInProgress fiber 的創建 是否可複用 current fiber 的節點。後續可再詳看 diff 算法。
-
workInProgress fiber tree 將確定要變更節點,渲染到屏幕上。
-
workInProgress fiber tree 晉升爲 current fiber tree。
參考資料
[1]
從內部瞭解現代瀏覽器 (3): https://juejin.cn/post/6844903687383416840
[2]
渲染樹構建、佈局及繪製: 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 實現: https://zh-hans.reactjs.org/docs/implementation-notes.html
[8]
React 算法之深度優先遍歷: https://juejin.cn/post/6912280245055782920
[9]
調用棧: https://segmentfault.com/a/1190000010360316
[10]
Leetcode: 斐波拉契數列: 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
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/F7Z-x3QNSWRUv0Gn67BxMA