課程:JavaScript 與 React 底層原理 第 14 堂:Fiber 執行機制
Lesson 14 note
本課程深入探討 React Fiber 的執行機制,解析工作循環如何實現可中斷渲染,以及 Fiber 樹如何透過深度優先遍歷(DFS)完成節點的展開與副作用收集。
工作循環與讓出控制
- Fiber 作為最小工作單元與虛擬堆疊幀(Virtual Stack Frame)
- 將執行狀態從瀏覽器的 Call Stack 轉移到堆積記憶體(Heap)中的 Fiber 物件,使 React 能隨時中斷並記錄進度。
workLoopConcurrent的核心邏輯- 透過
while迴圈檢查workInProgress指針與shouldYield()判定,實現協作式多工。
- 透過
- 時間切片(Time Slicing)機制
- React Scheduler 設定 5ms 為時間預算,在保證 JS 執行進度的同時,保留足夠空間給瀏覽器處理高優先級任務(如使用者輸入)。
- 使用
MessageChannel進行調度- 相比
setTimeout,MessageChannel沒有 4ms 的人工延遲,能更精準地在瀏覽器空閒時恢復渲染任務。
- 相比
beginWork 向下遍歷
beginWork的核心職責:向下展開 Fiber 樹- 接收當前節點(current)與工作節點(workInProgress),決定下一步的遍歷方向。
- 組件類型的分派與處理
- 根據 tag 區分函數元件、Host Component(原生標籤)等,並執行對應邏輯(如執行 Hooks 或獲取 children)。
- 協調比對(Reconciliation)與打標籤
- 將新產生的 React Element 與舊 Fiber 對比,在節點上標記副作用標籤(flags),如 Placement 或 Update。
- 可中斷性的實踐
beginWork每次僅處理一個節點並回傳第一個子節點,讓workLoop能在節點間隙檢查時間並隨時停工。
completeWork 向上收集
completeWork的核心職責:向上收網與組裝- 當
beginWork觸及葉節點(回傳null)時觸發,負責處理「歸」階段的邏輯。
- 當
- 建立與更新 DOM 實體(stateNode)
- 在記憶體中建立離線 DOM 節點,並執行
appendAllChildren將子節點先行組裝,提升 Commit Phase 的效能。
- 在記憶體中建立離線 DOM 節點,並執行
- 副作用標籤冒泡(Flags Bubbling)
- 父節點會收集子節點的 flags,並在 React 18+ 中利用
subtreeFlags記錄路徑資訊,使 Commit Phase 能夠精準導航到變更處。
- 父節點會收集子節點的 flags,並在 React 18+ 中利用
- 完整遍歷順序:下(Child)→ 右(Sibling)→ 上(Return)
- 透過非遞迴的循環實作 DFS,確保執行狀態可被儲存於全域指針中以供恢復。
Q&A
- Q: React 在暫停後是如何恢復渲染工作的?
- 核心在於全域指標
**workInProgress**的書籤作用
- 核心在於全域指標
- 即便
workLoop函數執行結束且 Call Stack 被清空,workInProgress指針依然保留在記憶體中,指向上次未處理完的 Fiber 節點。當瀏覽器透過MessageChannel回調恢復任務時,React 會直接從該指針位置繼續執行。 - Q: 為什麼要把副作用標籤(Flags)向上冒泡到根節點?
- 優化 Commit Phase 的遍歷效能
- 若不冒泡,Commit Phase 必須重新遍歷整棵樹來尋找有變動的節點(效能差)。透過冒泡與
subtreeFlags位元運算,React 只需要沿著有標記的路徑「導航」,就能極速找到需要操作真實 DOM 的位置。 - Q: DOM 節點的建立與掛載順序為何是「子節點先於父節點」?
- 由下而上的組裝策略(Post-order)
- 這種順序確保當父節點執行
completeWork時,其內部的子 DOM 樹已經在記憶體中建構完成。這讓 React 可以在離線狀態下拼裝好小型模組,最後在 Commit Phase 一次性掛載到網頁上,減少頻繁操作真實 DOM 的開銷。