博客 / 詳情

返回

【React源碼閲讀】React 渲染流程 —— 前置知識

系列文章:

  • 【React 源碼閲讀】為什麼 React Hooks 不能用條件語句來執行?
  • 【React 源碼閲讀】useCallback
  • 【React 源碼閲讀】Scheduler

1 寫在前面

React 源碼裏的概念實在是太多了,以至於如果真的要能完全理解源碼的話,我們就不得不提前瞭解一部分知識,不然看源碼的時候完全就是抓瞎。

2 Fiber

2.1 為什麼要有 Fiber

想象一下,如果你手頭上的事情分別有:

  • 帶娃👶(主線任務)
  • 打遊戲🎮(支線任務)

每次你在打遊戲🎮要花不少時間,一局遊戲不打完就算👶哭了也不能中斷,👶非常傷心(我這不爭氣的爹)。
雖然這是一個不恰當的比喻,但是這就是 React 15 的渲染體驗:

  • 任務一旦開始,無法中斷
  • UI 更新被卡住,用户體驗差
  • 無法分配不同優先級的任務

Fiber 架構下,帶娃👶和打遊戲🎮這兩件事就變成:

  • 時間分片:在帶娃👶的時候如果有空閒時間那麼我就可以打一會遊戲
  • 任務可中斷:一旦娃👶哭了,那麼我就立馬暫停手中的遊戲(去帶娃👶)
  • 任務可恢復:娃👶不哭了,那麼我又可以重新在暫停的地方開始遊戲🎮啦
  • Lane 模型:劃分優先級,帶娃👶這件事情上優先級是 No.1,其他事情先靠邊站🙄

2.2 概念

迴歸正題,Fiber 它有兩個含義:

  1. 數據結構

    • 本質是一個 JavaScript 對象
    • 每個組件實例對應一個 Fiber 節點
    • 保存組件的類型、props、state、副作用等信息
  2. 執行單元

    • React 把一次渲染工作拆分成很多小的 Fiber 任務(單元)
    • 這些單元可以分批執行、中途暫停、恢復、甚至丟棄

2.3 常見屬性及説明

Fiber 節點裏的屬性如下:

屬性名 類型説明 描述
tag number 節點類型,如 FunctionComponent、ClassComponent 等。
key string 或 null 用於 diff 的唯一標識。
elementType 任意 JSX 轉換後的原始類型,如函數、類、'div' 等。
type 任意 節點對應的組件類型或原始標籤名。
stateNode 對象或 null 對於 DOM 節點是真實 DOM,對於 class 是實例,函數組件為 null。
return Fiber 或 null 指向父節點。
child Fiber 或 null 第一個子節點。
sibling Fiber 或 null 下一個兄弟節點。
index number 在兄弟節點中的位置索引。
ref ref 對象或 null 組件 ref。
pendingProps 任意 本次渲染傳入的新 props。
memoizedProps 任意 上一次渲染的 props。
memoizedState 任意 上一次渲染的 state 或 hooks。
updateQueue 對象或 null setState 等更新隊列。
dependencies 對象或 null 記錄當前組件使用的 context。
mode number Fiber 的模式標誌,例如是否啓用 ConcurrentMode。
flags 位掩碼 當前節點本身的副作用標誌。
subtreeFlags 位掩碼 子樹副作用集合。
deletions Fiber[] 或 null 待刪除的子節點列表。
lanes 位掩碼 當前節點的優先級集合。
childLanes 位掩碼 子樹中包含的優先級合集。
alternate Fiber 或 null 另一份 Fiber 節點(workInProgress)。
actualDuration number Profiler 記錄的渲染耗時。
actualStartTime number Profiler 渲染開始時間。
selfBaseDuration number 自身渲染耗時。
treeBaseDuration number 子樹渲染總耗時。
_debugOwner Fiber 或 null DEV 環境用於調試的父組件。
_debugSource 對象或 null DEV 環境的源碼位置信息。

2.3 Fiber 樹

Fiber 樹就是以 Fiber 節點為最小單位組織成的樹結構,它用來描述 React 裏的頁面結構:

graph TD
  %% 主 Fiber 樹節點
  A["Fiber A"]
  B["Fiber B"]
  C["Fiber C"]
  D["Fiber D"]

  %% alternate Fiber 節點
  A2["Fiber A'"]
  B2["Fiber B'"]

  %% child 關係
  A -->|child| B
  B -->|child| C

  %% sibling 關係
  C -->|sibling| D

  %% return 關係
  B -->|return| A
  C -->|return| B
  D -->|return| B

  %% alternate 關係
  A ---|alternate| A2
  B ---|alternate| B2

3 Lane

3.1 概念

LaneReact 內部用於調度系統的一種優先級管理機制。每個 Lane 是一個二進制位(bit),多個 Lane 可以通過按位或(|)組合成一個 bitmask,從而實現多任務的並行調度與優先級控制。不同類型的更新任務(如同步更新、過渡動畫、輸入事件等)會被分配到不同的 Lane,React 會根據這些 Lane 的優先級來決定執行順序。

Lane名稱 二進制值 十進制值 説明
SyncLane 0b000...0001 1 同步更新(最高優先級,如 setState in flushSync
InputContinuousLane 0b000...0010 2 連續輸入事件(如拖動、滾動)
DefaultLane 0b000...0100 4 默認更新優先級
TransitionLanes 0b000...1xxx 8 ~ 0x800000 各類 transition,支持併發過渡,如 startTransition
  TransitionLane1 0b000...1000 8 第一個 transition lane
  TransitionLane2 0b000...1_0000 16 第二個 transition lane
...(共 16 個) ... ... 一直延續到 TransitionLane16(1 << 21)
RetryLanes ... 1 << 22 ~ 1 << 23 Suspense retry 後的更新
IdleLane 0b100...0000 1073741824 最低優先級,後台/預渲染任務
OffscreenLane 1 << 29 536870912 用於隱藏組件的更新(如 <Offscreen />

相應的,React 裏也將事件分門別類並且分別賦予了不同的優先級:

事件優先級名稱 對應的 Lane 常見對應事件類型(事件名) 描述
NoEventPriority NoLane 無(無任務) 沒有任務,空閒狀態
DiscreteEventPriority SyncLane 點擊(click)、鍵盤按鍵(keydown)、提交(submit)等離散事件 最高優先級,立即響應用户操作
ContinuousEventPriority InputContinuousLane 鼠標拖拽(mousemove)、滾動(scroll)、連續按鍵(keypress)等連續輸入事件 用户持續輸入,保持流暢體驗
DefaultEventPriority DefaultLane 定時器回調、異步請求完成等默認優先級事件 默認普通優先級
IdleEventPriority IdleLane 空閒回調、後台任務 低優先級,僅在主線程空閒時執行

3.2 相關方法

要理解源碼裏關於 Lane 操作的方法,首先就要理解 LanesLane 的區別。
本質上 LaneLanes 都是 number,但是 Lane 用來單獨表示一個優先級,而 Lanes 則用來表示多個優先級。

對比項 Lane Lanes
定義 單個優先級標識,表示一個更新任務的優先級位。 多個優先級的組合,表示當前包含哪些優先級的更新。
類型 number(通常是 2^n,即 0b000...1 形式) number(多個 Lane 的按位或運算結果)
二進制形式 僅一個位為 1,例如:0b000000000000000000000010 多個位可以為 1,例如:0b000000000000000000000110
作用 表示某一個具體的更新優先級 表示當前任務涉及的所有優先級
用途舉例 getHighestPriorityLane(lanes): Lane workInProgressRootRenderLanes: Lanes
常用操作 單獨判斷、與 Lanes 進行比較等 位運算(如合併:mergeLanes(a, b),判斷:includesSomeLane(lanes, lane)
使用場景 表示當前某個任務的目標優先級 表示當前某個 Fiber 或 Root 上所有掛起的任務優先級
是否複合類型 否(僅代表一個 bit) 是(可能包含多個 bit)

3.2.1 getHighestPriorityLane

export function getHighestPriorityLane(lanes: Lanes): Lane {
  return lanes & -lanes;
}

這個函數是用來獲取 Lanes 裏最高優先級的 Lane 來優先進行處理。
在二進制運算裏:-lanes = ~lanes + 1,因此 lanes & -lanes 就相當於 lanes & (~lanes + 1),所以我們可以拿到 lanes 最右邊的 1。
Lane 的設計裏,優先級是從左到右遞增的,因此最右邊的 Lane 優先級就是最高的。

3.2.2 mergeLanes

export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
  return a | b;
}

儘管 React 裏有優先級控制,但是往往同一個事件會有多個優先級疊加,這時候就需要將優先級提上來了,而 mergeLanes 是用來合併 2 個不同的優先級的。
舉例🌰:對於 001010 這兩個優先級,合併之後就變成了 011

3.2.3 removeLanes

removeLanes 顧名思義就是將一個子 lanesLanes 裏移除掉。

export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {
  return set & ~subset;
}

源碼裏這裏比較好理解,就是直接 set & ~subset,這樣返回的 Lanes 裏就不包含 subset 了。

3.2.4 intersectLanes

intersectLanes 就是取 ab 2 個 Lanes 的交集

export function intersectLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
  return a & b;
}

因為 Lane 是用二進制位的 1 來表示的,所以説用 & 操作之後,對應二進制位上沒有 1 的部分都會被去掉。

3.2.5 isSubsetOfLanes

判斷目標 Lane 是否為 Lanes 的子集。

export function isSubsetOfLanes(set: Lanes, subset: Lanes | Lane): boolean {
  return (set & subset) === subset;
}

簡單理解:如果 set & subset 的結果是 subset 的話,説明 setsubset 的交集是 subset,也就是説 subsetset 的子集。

3.2.6 includeSomeLane

判斷目標 Lanes 是否包含 Lane

export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane): boolean {
  return (a & b) !== NoLanes;
}

這個函數只判斷是否有交集,和上面的 intersectLanes 方法有些類似,但是返回的是 boolean

3.2.7 higherPriorityLane

返回優先級更高的 Lane:

export function higherPriorityLane(a: Lane, b: Lane): Lane {
  // This works because the bit ranges decrease in priority as you go left.
  return a !== NoLane && a < b ? a : b;
}

因為 Lane 是 bitmask 的設計,低位優先級更高,所以這裏判斷優先級的方法是判斷大小,數字越小優先級越高。

3.2.8 pickArbitraryLaneIndex

function pickArbitraryLaneIndex(lanes: Lanes) {
  return 31 - clz32(lanes);
}

clz32 是 JS 內置函數,全稱 Count Leading Zeros in 32-bit integer
它會返回 32 位無符號整數開頭有多少個連續的 0

因此 pickArbitraryLaneIndex 就是返回對應 Lanes 二進制位裏 1 的最高位,可以理解為拿到 Lanes 裏優先級最低的 Laneindex

3.2.9 getLanesOfEqualOrHigherPriority

獲取一個大於等於目標 LanesLane

function getLanesOfEqualOrHigherPriority(lanes: Lane | Lanes): Lanes {
  // Create a mask with all bits to the right or same as the highest bit.
  // So if lanes is 0b100, the result would be 0b111.
  // If lanes is 0b101, the result would be 0b111.
  const lowestPriorityLaneIndex = 31 - clz32(lanes);
  return (1 << (lowestPriorityLaneIndex + 1)) - 1;
}

前面提到 31 - clz32(lanes) 其實就是獲取 bitmask 裏最左位的 1 的 index
因此,如果 lanes0b100,那麼 lowestPriorityLaneIndex + 1 則為 3。
所以 1 << (lowestPriorityLaneIndex + 1) 則為 0b1000,減掉 1 之後返回的值為 0b0111
這樣一來,我們就可以通過這個函數來拿到“優先級相同或更高”的所有 lanes

4 總結

在這篇文章裏,我們對 Fiber架構和 Lane模型都有了初步的瞭解,對於後續繼續深入閲讀 React 源碼幫助非常大。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.