今天我們要說明 React 16+ 能實現非阻塞渲染 (Non-blocking Rendering) 與可中斷更新 (Interruptible Render) 的核心底層:Fiber 雙緩衝機制 (Double Buffering) 與 WorkLoop 調度核心。
在 React 15 及更早版本中,協調器 (Reconciler) 採用的是遞迴同步運算的 Stack Reconciler。
痛點:一旦觸發更新,React 會一氣呵成、無法中斷地遞迴比對整棵 Virtual DOM 樹。當組件結構龐大時,CPU 運算時間若超過 16ms,就會導致瀏覽器主線程 (Main Thread) 無法及時處理使用者輸入或動畫格,出現肉眼可見的掉幀與卡頓 (Jank)。
解法:React 16 將 Stack 拆解為以鏈表 (Linked List) 為基礎的 Fiber Node,把一次龐大的更新任務拆分成無數個微小的 Work Unit(工作單元),實現可排程、可中斷、可復原的調度機制。
為了確保在背景處理複雜計算時畫面不會閃爍或出現半成品 UI,React 借鏡了圖形學中的 Double Buffering 技術,在記憶體中同時維護兩棵 Fiber 樹:
current 樹:代表當前螢幕上已渲染出來的 UI 畫面。
workInProgress (wip) 樹:代表正在背景進行 Reconcile 計算、準備渲染的下一幀 UI 畫面。
[螢幕顯示 (UI)]
│
▼
current Fiber 樹 ───(alternate)───► workInProgress Fiber 樹 (背景建構中)
alternate 指針:current 節點與 workInProgress 節點透過 alternate 屬性互相引用,實現雙向連接。
Commit 階段一秒切換:當 workInProgress 樹在背景計算 completion 後,React 只要將根節點 (HostRoot) 的 current 指針切換指向 workInProgress 樹,就能在極短時間內完成 UI 替換!
React 的調度核心就是 WorkLoop。它會在瀏覽器的空閒時間(透過 Scheduler 模組排程)逐步執行 Fiber 節點的比對:
// 簡化版 React 內部 WorkLoop 邏輯
function workLoopConcurrent() {
// 只要還有剩餘 Work Unit 且 Scheduler 判定當前 Time Slice 尚未逾期,就繼續執行
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
shouldYield()(時間片 Time Slicing):每一個 Time Slice 預設約 5ms。當 5ms 耗盡或有高優先級事件(如使用者打字、點擊)進入時,shouldYield() 會回傳 true,主動讓出主線程給瀏覽器回應使用者。
三向鏈表遍歷:Fiber 節點透過 child(第一個子節點)、sibling(下一個兄弟節點)、return(父節點)三個指針連接,讓 React 可以隨時中斷並記錄「當前處理到哪一個 Fiber 節點」,下次恢復時從該節點繼續。
React 內部透過 Lane 模型(31 位元的位元遮罩 Bitmask)來標記各個更新任務的優先級:
SyncLane / InputContinuousLane:高優先級(如離散點擊 onClick、文字輸入 onChange),必須立刻處理。
DefaultLane:中優先級(如 API 資料回傳觸發的更新)。
TransitionLane / OffscreenLane:低優先級(如頁面切換動畫、隱藏內容預載入),允許被高優先級任務插隊 (Interruption)。
當高優先級任務插隊時,React 會中斷並放棄當前計算到一半的低優先級 workInProgress 樹;等待高優先級任務 Commit 到畫面後,再重新啟動低優先級任務。