今天我們要深入拆解 React 的兩個核心處理階段——Render 階段(計算) 與 Commit 階段(渲染),並看看 Reconciler(協調器) 與 Renderer(渲染器) 是如何分工協作的。
React 採用了模組化的架構設計,將「計算差異」與「繪製 DOM」徹底解耦:
Reconciler(如 react-reconciler):
負責維護 WorkLoop、處理 Hooks、執行 Diffing 演算法,並構建 Fiber 樹。
特點:純 JavaScript 運算,平台無關(Platform-agnostic)。
Renderer(如 react-dom / react-native):
負責將 Reconciler 計算好的變更實際寫入目標平台的 UI 上(如瀏覽器 DOM 或原生 iOS/Android View)。
特點:平台相關(Platform-specific)。
Render 階段的核心任務是:遞迴走訪 Fiber 樹,計算出哪裡發生變更,並在 Fiber 節點上記錄 Side Effect 標記(flags / subtreeFlags)。
這個階段包含兩個關鍵動作:
[HostRoot]
/ ▲
beginWork completeWork
/ \
[Child] ──► [Sibling]
beginWork(current, workInProgress, renderLanes)
向下走訪:從根節點一路往下找尋變更的節點。
主要任務:執行組件函數、比對 Props 與 State、處理 Hooks、呼叫 reconcileChildren(執行 Diffing 演算法),並為需要更新的 Fiber 節點打上 flags(如 Placement 插入、Update 更新、Deletion 刪除)。
completeWork(current, workInProgress, renderLanes)
向上回溯:當節點沒有子節點時,呼叫 completeWork 並向上回溯。
主要任務:建立 DOM 實例(stateNode)、處理屬性賦值(如 className、onClick),並將子節點的 flags 向上收集冒泡到父節點的 subtreeFlags。
當 workInProgress 樹在 Render 階段完全走訪完畢後,React 會進入 Commit 階段。這個階段是同步且不可中斷的,因為畫面更新必須一次到位,避免使用者看到半成品的 UI。
Commit 階段可以拆解為三大子子階段 (Sub-phases):
┌─────────────────────────────────────────────────────────────┐
│ 1. Before Mutation 階段:在操作 DOM 前(如執行 getSnapshotBeforeUpdate)│
├─────────────────────────────────────────────────────────────┤
│ 2. Mutation 階段:真正操作 DOM(根據 flags 進行 插入/更新/刪除) │
├─────────────────────────────────────────────────────────────┤
│ 3. Layout 階段:DOM 已寫入(執行 useLayoutEffect、更新 ref) │
└─────────────────────────────────────────────────────────────┘
* 切換根節點 current 指針 (current = workInProgress)
* 畫面上線後非同步呼叫 useEffect
Before Mutation 階段:
遍歷 Effect 鏈表,觸發 Class Component 的 getSnapshotBeforeUpdate。
準備調度非同步的 useEffect。
Mutation 階段(最關鍵):
Renderer 開始登場!根據 Fiber 上標記的 flags,真正呼叫 appendChild、removeChild 或 setAttribute 修改真實 DOM。
執行舊組件的 useEffect Cleanup 函數。
Layout 階段:
在此時刻,根節點 pointer 正式切換(current = workInProgress)。
同步執行 useLayoutEffect 的 Callback。
綁定與更新 useRef 的 current 屬性。
Commit 階段完成後,主線程交還給瀏覽器進行排版與繪製(Reflow / Repaint)。隨後,React 會非同步觸發 useEffect 的設定函數。
理解了 Render/Commit 階段後,這兩個 Hook 的根本差異就一目了然了: