目錄
一、虛擬 DOM
二、Diffing 算法
三、Fiber 架構
四、渲染流程
1. Render 階段(可中斷異步過程)
2. Commit 階段(同步不可中斷)
五、時間切片(Time Slicing)
六、核心流程步驟總結
1. 狀態更新觸發
2. Render 階段(異步可中斷,構建 Fiber 樹)
3. Commit 階段(同步不可中斷,更新真實 DOM)
4. 雙緩存機制切換
5. 調度系統核心支撐
七、組件觸發渲染的時機
相關內容:?
React Fiber 架構原理:關于 Fiber 樹的一切
React核心原理淺析
二十分鐘掌握React核心理念,老鳥快速入門指南
一、虛擬 DOM
React 使用虛擬 DOM 來表示 UI 的狀態。虛擬 DOM 是一個輕量級的 JavaScript 對象,每個節點包含?tag
(標簽名)、props
(屬性)、children
(子節點)。
核心價值在于性能優化 —— 直接操作真實 DOM 會觸發瀏覽器重排 / 重繪,成本很高;而虛擬 DOM 先在內存中通過 Diff 算法比對狀態變更,再批量更新真實 DOM,減少瀏覽器操作次數。
二、Diffing 算法
Diffing 算法用于比較新舊虛擬 DOM 樹,以最小的操作次數將舊 DOM 樹轉換為新 DOM 樹。
React 提出了復雜度為 O(n) 的啟發式算法,通過設置 key 屬性來標識一組同級子元素,從而高效地更新真實 DOM。
- 同層比較(Tree Diff):僅逐層對比節點,不跨層級遍歷。若節點類型不同,直接銷毀整棵子樹及其組件實例并重建。
- Key 值優化:列表渲染時需為元素提供唯一?
key
,用于標識節點身份。通過?key
,React 可識別節點的移動或復用,避免全量更新。 - 組件類型比對:若組件類型相同,則遞歸更新子節點;若類型不同,則卸載舊組件并掛載新組件。
不指定 key 的后果:
React 通過 ??key + 組件類型?? 的組合來識別元素的身份,沒有 key 時默認使用 ??數組索引?? 作為 key。
若用索引作為 key,當列表項順序變化或中間插入 / 刪除元素時,React 會錯誤地認為大量節點需重新創建,而非移動或更新,導致不必要的 DOM 操作(如重復卸載 / 掛載組件),嚴重影響性能。
索引作為 key 的問題本質:
- ??綁定問題??:使用索引作為 key 時狀態綁定到??位置??,而非??數據???
- DOM復用規則??:React 只復用相同 key 對應的 DOM 節點
- ??數據與DOM分離??:React 更新內容但不更新狀態
- ??狀態漂移??:輸入狀態留在原位置,被新元素繼承
使用唯一 ID 作為 key 可以解決這個問題,因為它確保狀態與數據項(而非位置)保持一致關聯。
三、Fiber 架構
Fiber 架構解決了傳統同步渲染阻塞主線程的問題,實現可中斷的異步渲染,支持時間切片和優先級調度。
Fiber 節點結構:
每個組件對應一個 Fiber 節點,構成鏈表樹(非傳統遞歸樹)。節點包含組件類型、狀態、副作用標記(effectTag
,如刪除、新增節點)、節點指針:child
(指向第一個子節點)、sibling
(指向下一個兄弟節點)、return
(指向父節點)。
雙緩存機制:
- Current Tree:當前已渲染到頁面的 Fiber 樹。
- WorkInProgress Tree:后臺構建的新 Fiber 樹,用于計算變更。
兩棵樹通過?alternate
?指針關聯,每次更新時新建?WorkInProgress Tree
,構建完成后直接替換Current Tree
,保證視圖連續性。
四、渲染流程
1. Render 階段(可中斷異步過程)
構建 Fiber 鏈表樹,通過 Diff 標記副作用(如節點增刪)。
1. 深度優先遍歷
從根節點開始,采用深度優先遍歷,通過?beginWork
?向下處理每個 Fiber 節點,逐步構建 Fiber 鏈表樹。
2. Diffing 算法執行
對比新舊子節點,決定復用/移動/刪除,并標記?effectTag
(如?Placement
?移動節點)。
- 節點復用條件:父節點已復用,且?
key
?和?type
?相同。 - 子節點 Diff 順序:先嘗試單節點匹配,再處理多節點末尾增刪(一輪循環),最后處理復雜移動場景(二輪循環),盡可能減少節點移動開銷。
3. 向上回溯
當節點無子節點時,進入?completeUnitOfWork,
自底向上收集?effectTag
,將子節點的?effectList
?合并到父節點,最終形成從根節點到葉節點的副作用鏈表。
4. 中斷與恢復
利用時間切片(Time Slicing)將任務拆分為微任務,在瀏覽器空閑時執行,避免阻塞主線程。每處理完一個節點,檢查剩余時間片,時間耗盡時暫停,通過全局變量保存進度,瀏覽器空閑時通過調度器恢復任務。
2. Commit 階段(同步不可中斷)
批量執行副作用,更新真實 DOM。此階段必須一氣呵成,確保 DOM 操作的原子性。
遍歷?effectList
,批量更新真實 DOM(執行?effectTag
?對應的操作,如創建、刪除節點)。觸發回調,處理 ref 和 useEffect。
五、時間切片(Time Slicing)
時間切片策略將渲染任務拆分為微任務單元,利用瀏覽器空閑時段執行,提升響應性。
調度器:模擬?requestIdleCallback
?功能(兼容舊瀏覽器),設置任務優先級,通過?MessageChannel
?實現異步調度,將任務拆分為小單元,每次執行完一個單元后,檢查是否有高優先級任務插隊,若有則暫停當前任務。
任務優先級:分為五級(緊急交互 > 過渡動畫 > 普通更新 > 延遲更新 > 過期任務),高優先級任務可插隊執行,中斷低優先級任務;低優先級任務可暫停或丟棄,避免占用主線程。例如,用戶點擊按鈕時,渲染任務會被暫停,優先處理點擊回調。
調度系統通過四層架構實現:
- SchedulerHostConfig:對接瀏覽器底層能力,利用?
MessageChannel
?計算空閑時間,提供空閑回調機制,是 Fiber 調度的基礎。 - Scheduler:核心任務管理模塊,定義五級優先級,通過雙向循環鏈表維護任務池,實現任務的注冊、取消和優先級排序,并在瀏覽器空閑時執行任務。
- SchedulerWithReactIntegration:抹平調度接口,將 Scheduler 與 React 的更新流程整合,例如在狀態更新時觸發調度。
- ReactFiberScheduler:應用層調度入口,將 React 的更新任務(如 Fiber 樹的構建)包裝為 Scheduler 可處理的任務,在 Render 階段通過?
shouldYield()
?檢查是否需要中斷任務,確保主線程不阻塞。
六、核心流程步驟總結
1. 狀態更新觸發
- 因用戶交互(如點擊)、
setState
?或 Hooks 更新函數調用,觸發組件狀態變更,生成新虛擬 DOM,啟動更新流程。
2. Render 階段(異步可中斷,構建 Fiber 樹)
- 任務拆分與優先級調度:利用?時間切片?將渲染任務拆分為微任務,通過調度器按優先級異步執行,可被高優先級任務中斷。
- Fiber 樹構建與 Diff 執行:
- 深度優先遍歷,通過?
beginWork
?對比新舊虛擬 DOM,復用?key
?和類型相同的節點,標記新增 / 更新 / 刪除的?副作用(effectTag)。 - 子節點 Diff 按 “單節點匹配 → 末尾增刪 → 復雜移動” 順序優化,減少 DOM 操作。
- 深度優先遍歷,通過?
- 副作用收集:通過?
completeUnitOfWork
?自底向上合并副作用,形成根節點的?effectList 鏈表。 - 中斷與恢復:每處理完一個 Fiber 節點,檢查時間片是否耗盡,耗盡時暫停任務并保存進度,瀏覽器空閑時恢復。
3. Commit 階段(同步不可中斷,更新真實 DOM)
- 遍歷?
effectList
,批量執行 DOM 操作(創建、刪除、更新節點),確保操作原子性。 - 觸發生命周期回調(如?
useEffect
、ref
?更新),完成視圖渲染。
4. 雙緩存機制切換
- 構建完成的?
WorkInProgress Tree
?替換為?Current Tree
,通過?alternate
?指針復用節點數據,保證視圖連續性。
5. 調度系統核心支撐
- 通過?四層架構(SchedulerHostConfig、Scheduler、SchedulerWithReactIntegration、ReactFiberScheduler)實現任務優先級管理、時間切片和異步調度,避免主線程阻塞。
七、組件觸發渲染的時機
- 狀態(state)更新:調用更新函數導致組件狀態變化。
- props 變化:父組件傳遞的 props 值或引用發生改變。
- 上下文(Context)變化:組件依賴的 Context 值更新。
- 父組件渲染:父組件重新渲染觸發子組件默認更新(未優化時)。
- 強制更新:調用
forceUpdate()
跳過常規更新判斷。 - 組件 key 變化:觸發舊組件卸載和新組件掛載(相當于重新渲染)。