# React 优先级管理
React 中的可中断渲染,时间切片(time slicing),异步渲染(suspense)等特性, 在源码中得以实现都依赖于 React 的优先级管理。React
内部对于 优先级
的管理, 根据功能的不同分为 LanePriority
, SchedulerPriority
, ReactPriorityLevel
3 种类型(2套优先级体系
和1套转换体系
):
fiber
优先级(LanePriority
): 位于react-reconciler
包, 也就是Lane(车道模型)
(opens new window)。- 调度优先级(
SchedulerPriority
): 位于scheduler
包。 - 优先级等级(
ReactPriorityLevel
) : 位于react-reconciler
包中的SchedulerWithReactIntegration.js
(opens new window), 负责上述 2 套优先级体系的转换。
在深入分析 3 种优先级之前, 为了深入理解LanePriority
, 需要先了解Lane
, 这是react@17.0.0
的新特性。
之前提到过Scheduler
与React
是两套优先级
机制。在React
中,存在多种使用不同优先级
的情况,比如(Concurrent Mode
开启情况):
- 过期任务或者同步任务使用
同步
优先级 - 用户交互产生的更新(比如点击事件)使用高优先级
- 网络请求产生的更新使用一般优先级
Suspense
使用低优先级
所以,React
需要设计一套满足如下需要的优先级
机制:
- 可以表示
优先级
的不同 - 可能同时存在几个同
优先级
的更新
,所以还得能表示批
的概念 - 方便进行
优先级
相关计算
为了满足如上需求,React
设计了lane
模型。
# Lane (车道模型)
首先引入作者对Lane
的解释(相应的 pr (opens new window)), 这里简单概括如下:
Lane
类型被定义为二进制变量, 利用了位掩码的特性, 在频繁运算的时候占用内存少, 计算速度快。Lane
和Lanes
就是单数和复数的关系, 代表单个任务的定义为Lane
, 代表多个任务的定义为Lanes
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
2
3
4
5
- 使用
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;
2
3
4
5
6
7
8
9
10
11
12
13
Lanes
使用单个 32 位二进制变量即可代表多个不同的任务,也就是说一个变量即可代表一个组(group
),如果要在一个 group 中分离出单个 task,非常容易。- 在
expirationTime
模型设计之初, react 体系中还没有Suspense 异步渲染 (opens new window)的概念。现在有如下场景: 有 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
//1. 通过expirationTime实现
// 0) 维护一个链表, 按照单个task的优先级顺序进行插入
// 1) 删除单个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 >= lowestPriorityInRange;
// 2. 通过Lanes实现
// 1) 删除单个task
batchOfTasks &= ~task;
// 2) 增加单个task
batchOfTasks |= task;
// 3) 比较task是否在group中
const isTaskIncludedInBatch = (task & batchOfTasks) !== 0;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
通过上述伪代码, 可以看到Lanes
的优越性,运用起来代码量少,简洁高效,这与其大量使用位运算脱不了关系。
Lanes
是一个不透明的类型, 只能在ReactFiberLane.js
(opens new window)这个模块中维护. 如果要在其他文件中使用, 只能通过ReactFiberLane.js
中提供的工具函数来使用.
分析车道模型的源码(ReactFiberLane.js
(opens new window)中), 可以得到如下结论:
可以使用的比特位一共有 31 位。
共定义了18 种车道(
Lane/Lanes
)变量 (opens new window), 每一个变量占有 1 个或多个比特位, 分别定义为Lane
和Lanes
类型.每一种车道(
Lane/Lanes
)都有对应的优先级, 所以源码中定义了 18 种优先级(LanePriority (opens new window)).占有低位比特位的
Lane
变量对应的优先级越高- 最高优先级为
SyncLanePriority
对应的车道为SyncLane = 0b0000000000000000000000000000001
. - 最低优先级为
OffscreenLanePriority
对应的车道为OffscreenLane = 0b1000000000000000000000000000000
- 最高优先级为
# 3 种优先级的联系
React
内部对于优先级
的管理, 根据功能的不同分为LanePriority
, SchedulerPriority
, ReactPriorityLevel
3 种类型:
LanePriority
和SchedulerPriority
从命名上看, 它们代表的是优先级
ReactPriorityLevel
从命名上看, 它代表的是等级
而不是优先级, 它用于衡量LanePriority
和SchedulerPriority
的等级
# LanePriority
LanePriority
:属于react-reconciler
,定义于ReactFiberLane.js
(见源码 (opens new window)).
export const SyncLanePriority: LanePriority = 15;
export const SyncBatchedLanePriority: LanePriority = 14;
const InputDiscreteHydrationLanePriority: LanePriority = 13;
export const InputDiscreteLanePriority: LanePriority = 12;
// .....
const OffscreenLanePriority: LanePriority = 1;
export const NoLanePriority: LanePriority = 0;
2
3
4
5
6
7
与 fiber
构造过程相关的优先级(如 fiber.updateQueue
,fiber.lanes
)都使用 LanePriority
。
# SchedulerPriority
SchedulerPriority
,属于scheduler
包,定义于SchedulerPriorities.js
中(见源码 (opens new window)).
export const NoPriority = 0;
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;
2
3
4
5
6
与 scheduler
调度中心相关的优先级使用 SchedulerPriority
。
# ReactPriorityLevel
reactPriorityLevel
, 属于react-reconciler
,定义于 SchedulerWithReactIntegration.js
中(见源码 (opens new window)).
export const ImmediatePriority: ReactPriorityLevel = 99;
export const UserBlockingPriority: ReactPriorityLevel = 98;
export const NormalPriority: ReactPriorityLevel = 97;
export const LowPriority: ReactPriorityLevel = 96;
export const IdlePriority: ReactPriorityLevel = 95;
// NoPriority is the absence of priority. Also React-only.
export const NoPriority: ReactPriorityLevel = 90;
2
3
4
5
6
7
LanePriority
与 SchedulerPriority
通过 ReactPriorityLevel
进行转换。
# 转换关系
为了能协同调度中心( scheduler
包)和 fiber 树构造( react-reconciler
包)中对优先级的使用, 则需要转换 SchedulerPriority
和 LanePriority
, 转换的桥梁正是 ReactPriorityLevel
。
在SchedulerWithReactIntegration.js
中 (opens new window), 可以互转SchedulerPriority
和 ReactPriorityLevel
:
// 把 SchedulerPriority 转换成 ReactPriorityLevel
export function getCurrentPriorityLevel(): ReactPriorityLevel {
switch (Scheduler_getCurrentPriorityLevel()) {
case Scheduler_ImmediatePriority:
return ImmediatePriority;
case Scheduler_UserBlockingPriority:
return UserBlockingPriority;
case Scheduler_NormalPriority:
return NormalPriority;
case Scheduler_LowPriority:
return LowPriority;
case Scheduler_IdlePriority:
return IdlePriority;
default:
invariant(false, 'Unknown priority level.');
}
}
// 把 ReactPriorityLevel 转换成 SchedulerPriority
function reactPriorityToSchedulerPriority(reactPriorityLevel) {
switch (reactPriorityLevel) {
case ImmediatePriority:
return Scheduler_ImmediatePriority;
case UserBlockingPriority:
return Scheduler_UserBlockingPriority;
case NormalPriority:
return Scheduler_NormalPriority;
case LowPriority:
return Scheduler_LowPriority;
case IdlePriority:
return Scheduler_IdlePriority;
default:
invariant(false, 'Unknown priority level.');
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
在ReactFiberLane.js
中 (opens new window), 可以互转 LanePriority
和 ReactPriorityLevel
:
export function schedulerPriorityToLanePriority(
schedulerPriorityLevel: ReactPriorityLevel,
): LanePriority {
switch (schedulerPriorityLevel) {
case ImmediateSchedulerPriority:
return SyncLanePriority;
// ... 省略部分代码
default:
return NoLanePriority;
}
}
export function lanePriorityToSchedulerPriority(
lanePriority: LanePriority,
): ReactPriorityLevel {
switch (lanePriority) {
case SyncLanePriority:
case SyncBatchedLanePriority:
return ImmediateSchedulerPriority;
// ... 省略部分代码
default:
invariant(
false,
'Invalid update priority: %s. This is a bug in React.',
lanePriority,
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28