優先級類型
- React內部對于優先級的管理,貫穿運作流程的4個階段(從輸入到輸出),根據其功能的不同,可以分為3種類型:
- 1 )fiber優先級(LanePriority)
- 位于 react-reconciler包,也就是Lane(車道模型)
- 2 )調度優先級(SchedulerPriority)
- 位于scheduler包
- 3 )優先級等級(ReactPriorityLevel)
- 位于react-reconciler包中的 SchedulerWithReactIntegration.js
- 負責上述2套優先級體系的轉換.
- 1 )fiber優先級(LanePriority)
- Lane 是在 react@17.0.0的新特性.
Lane(車道模型)
-
英文單詞lane翻譯成中文表示"車道,航道"的意思,所以很多文章都將Lanes模型稱為車道模型
-
Lane模型的源碼在 ReactFiberLane.js,源碼中大量使用了位運算
-
首先引入對Lane的解釋, 這里簡單概括如下:
- 1 )Lane類型被定義為二進制變量,利用了位掩碼的特性,在頻繁運算的時候占用內存少,計算速度快.
- Lane和Lanes就是單數和復數的關系,代表單個任務的定義為Lane,代表多個任務的定義為Lanes
- 2 )Lane是對于expirationTime的重構,以前使用expirationTime表示的字段,都改為了lane
renderExpirationTime -> renderlanes update.expirationTime -> update.lane fiber.expirationTime -> fiber.lanes fiber.childExpirationTime -> fiber.childLanes root.firstPendingTime and root.lastPendingTime -> fiber.pendingLanes
- 3 )使用Lanes模型相比expirationTime模型的優勢
- Lanes把任務優先級從批量任務中分離出來
- 可以更方便的判斷單個任務與批量任務的優先級是否重疊
// 判斷:單task與batchTask的優先級是否重疊 // 1.通過expirationTime判斷 const isTaskIncludedInBatch = priorityOfTask >= priorityOfBatch; // 2.通過Lanes判斷 const isTaskIncludedInBatch =(task & batchOfTasks) !== 0;// 當同時處理一組任務,該組內有多個任務,且每個任務的優先級不一致 // 1. 如果通過expirationTime判斷,需要維護一個范圍(在Lane重構之前,源碼中就是這樣比較的) const isTaskIncludedInBatch =taskPriority <= highestPriorityInRange &&taskPriority >= lowestPriorityInRange; // 2. 通過Lanes判斷 const isTaskIncludedInBatch = (task & batchOfTasks) !== 0;
- 1 )Lane類型被定義為二進制變量,利用了位掩碼的特性,在頻繁運算的時候占用內存少,計算速度快.
-
Lanes使用單個32位二進制變量即可代表多個不同的任務
-
也就是說一個變量即可代表一個組(group)
-
如果要在一個group中分離出單個task,非常容易
-
在expirationTime模型設計之初,react體系中還沒有 Suspense 異步渲染的概念
-
現在有如下場景:有3個任務,其優先級A>B>C,正常來講只需要按照優先級順序執行就可以了
-
但是現在情況變了:A和C任務是CPU密集型,而B是IO密集型(Suspense會調用遠程api,算是IO任務)
-
即A(cpu)>B(IO)>C(cpu).此時的需求需要將任務B從group中分離出來,先處理cpu任務A和C
//從group中刪除或增加task// 通過expirationTime實現 // 維護一個鏈表,按照單個task的優先級順序進行插入 // 刪除單個task(從鏈表中刪除一個元素) task. prev.next = task.next; //2)增加單個task(需要對比當前task的優先級,插入到鏈表正確的位置上) let current = queue; while (task.expirationTime >= current.expirationTime) {current = current.next; } task.next = current.next; current.next = task; //3)比較task是否在group中 const isTaskIncludedInBatch =taskPriority <= highestPriorityInRange &&taskPriority >= lowestPriorityInRang; //2.通過Lanes實現 //1)刪除單個task batchOfTasks &= ~task; //2)增加單個task batchOfTasks |= task; //3)比較task是否在group中 const isTaskIncludedInBatch = (task & batchOfTasks) !== 0;
-
Lanes是一個不透明的類型,只能在ReactFiberLane.js這個模塊中維護
-
如果要在其他文件中使用,只能通過 ReactFiberLane.js 中提供的工具函數來使用
-
分析車道模型的源碼(ReactFiberLane.js中),可以得到如下結論:
- 1.可以使用的比特位一共有31位
- 2.共定義了18種車道(Lane/Lanes)變量,每一個變量占有1個或多個比特位,分別定義為Lane和Lanes類型.
- 3.每一種車道(Lane/Lanes)都有對應的優先級,所以源碼中定義了18種優先級(LanePriority).
- 4.占有低位比特位的Lane變量對應的優先級越高
- 最高優先級為 SynclanePriority 對應的車道為
Synclane = 0b0000000000000000000000000000001
- 最低優先級為 OffscreenLanePriority 對應的車道為
OffscreenLane = 0b1000000000000000000000000000000
- 最高優先級為 SynclanePriority 對應的車道為
位運算
-
什么是位運算?程序中的所有數在計算機內存中都是以二進制的形式儲存的。
-
位運算就是直接對整數在內存中的二進制位進行操作。
-
比如
- 0 在二進制中用 0 表示,我們用 0000 代表;
- 1 在二進制中用 1 表示,我們用 0001 代表;
-
那么先看兩個位運算符號 & 和 |
- & 對于每一個比特位,兩個操作數都為 1 時,結果為 1,否則為 0
- | 對于每一個比特位,兩個操作數都為0時,結果為 0,否則為 1
- 我們看一下兩個 1 & 0 和 1 | 0
- 如上 1 & 0 = 0, 1 | 0 = 1
-
參考
0 00001 0001-------------0 & 1 = 0000 = 0
-
再參考
0 0000 1 0001 ---------------- 0 | 1 = 0001 = 1
使用一張表格詳細說明
運算符 | 用法 | 描述 |
---|---|---|
與 & | a & b | 如果兩位都是1則設置每位為1 |
或 | | a | b | 如果兩位之一為1則設置每位為1 |
異或 ^ | a^b | 如果兩位只有一位為1則設置每位為1 |
非 ~ | ~ a | 反轉操作數的比特位, 即0變成1, 1變成0 |
左移(<<) | a << b | 將a的二進制形式向左移b(< 32)比特位, 右邊用0填充 |
有符號右移(>>) | a>>b | 將a的二進制形式向右移b(<32)比特位,丟棄被移除的位,左側以最高位來填充 |
無符號右移(>>>) | a>>>b | 將a的二進制形式向右移b(<32)比特位,丟棄被移除的位,并用0在左側填充 |
位運算的一個使用場景
- 比如有一個場景下,會有很多狀態常量A,B,C…,這些狀態在整個應用中在一些關鍵節點中做流程控制
- 比如:
if(value === A) {// TODO.. }
- 如上判斷value等于常量A,那么進入到if的條件語句中
- 此時是value屬性是簡單的一對一關系,但是實際場景下value可能是好幾個枚舉常量的集合
- 也就是一對多的關系,那么此時value可能同時代表A和B兩個屬性
- 如下圖所示:

- 這時候,如果按照下面的代碼來寫,就會很麻煩
if(value === A || value === B) {// TODO.. }
- 此時的問題就是如何用一個value表示A和B兩個屬性的集合,這個時候位運算就派上用場了
- 因為可以把一些狀態常量用32位的二進制來表示(這里也可以用其他進制),比如:
const A = 0b0000000000000000000000000000001 // 優先級最高 const B = 0b0000000000000000000000000000010 const C = 0b0000000000000000000000000000100 // 優先級最低
- 通過移位的方式讓每一個常量都單獨占一位,這樣在判斷一個屬性是否包含常量的時候
- 可以根據當前位數的1和0來判斷
- 這樣如果一個值即代表A又代表B那么就可以通過位運算的 | 來處理
- 就有
AB = A | B = 0b0000000000000000000000000000011
- 那么如果把AB的值賦予給value,那么此時的value就可以用來代表A和B
- 此時當然不能直接通過等于或者恒等來判斷value是否為A或者B,此時就可以通過&來判斷。具體實現如下:
const A = 0b0000000000000000000000000000001 const B = 0b0000000000000000000000000000010 const C = 0b0000000000000000000000000000100 const N = 0b0000000000000000000000000000000 const value = A | B console.log((value & A ) !== N) // true console.log((value & B ) !== N) // true console.log((value & C ) !== N ) // false
位運算在 react 中的應用
export const NoLanes = /* */ 0b0000000000000000000000000000000;
const SyncLane = /* */ 0b0000000000000000000000000000001;const InputContinuousHydrationLane = /* */ 0b0000000000000000000000000000010;
const InputContinuousLane = /* */ 0b0000000000000000000000000000100;const DefaultHydrationLane = /* */ 0b0000000000000000000000000001000;
const DefaultLane = /* */ 0b0000000000000000000000000010000;const TransitionHydrationLane = /* */ 0b0000000000000000000000000100000;
const TransitionLane = /* */ 0b0000000000000000000000001000000;
-
如上 SyncLane 代表的數值是1,它卻是最高的優先級
-
也即是說lane的代表的數值越小,此次更新的優先級就越大
-
在新版本的React中,還有一個新特性,就是render階段可能被中斷,在這個期間會產生一個更高優先級的任務
-
那么會再次更新lane屬性,這樣多個更新就會合并,這樣一個lane可能需要表現出多個更新優先級
-
我們來看一下React是如何通過位運算分離出優先級的
function getHighestPriorityLane(lanes) {return lanes & -lanes; }
-
如上就是通過 lanes & -lanes 分離出最高優先級的任務的,我們來看一下具體的流程
- 比如SyncLane和InputContinuousLane合并之后的任務優先級lane為
SyncLane = 0b0000000000000000000000000000001
InputContinuousLane = 0b0000000000000000000000000000100
lane = SyncLane|InputContinuousLane
lane = 0b0000000000000000000000000000101
- 那么通過 lanes & -lanes 分離出 SyncLane
- 首先我們看一下 -lanes,在二進制中需要用補碼表示為:
-lane = 0b1111111111111111111111111111011
- 那么接下來執行 lanes & -lanes 看一下,& 的邏輯是如果兩位都是1則設置改位為1,否則為0
- 那么 lane & -lane, 只有一位(最后一位)全是1,所有合并后的內容為:
lane & -lane = 0b0000000000000000000000000000001
-
可以看得出來lane&-lane的結果是SyncLane,所以通過lane&-lane就能分離出最高優先級的任務
const SyncLane = 0b0000000000000000000000000000001 const InputContinuousLane = 0b0000000000000000000000000000100 const lane = SyncLane | InputContinuousLane console.log((lane & -lane) === SyncLane ) // true