优先级
React 更新过程中涉及到与优先级相关的处理,通过优先级的灵活运用,React 实现了可中断渲染,时间切片(time slicing),异步渲染(suspense)等特性。
优先级架构下,高优先级任务可以打断低优先级任务。当用户操作界面时,例如搜索、点击、下拉等操作,为了避免页面卡顿,需要让出线程的执行权,先执行用户触发的事件(高优先级任务),其它不那么重要的事件(低优先级任务)待高优先级任务执行完毕后再执行。React 最开使用 ExpirationTime
作为任务优先级的衡量,后续将 ExpirationTime
切换成了 Lane
优先级。
优先级分类
- 事件优先级:按照用户事件的交互紧急程度,划分的优先级
- 更新优先级:事件导致 React 产生的更新对象(update)的优先级(update.lane)
- 任务优先级:产生更新对象之后,React 去执行一个更新任务,这个任务所持有的优先级
- 调度优先级:Scheduler 依据 React 更新任务生成一个调度任务,这个调度任务所持有的优先级
Lane 优先级
Lane 类型被定义为二进制变量,利用了位掩码的特性,在频繁运算的时候占用内存少,计算速度快。
Lanes 是对于 expirationTime 的重构,相比 expirationTime 的优势是:
- Lanes 把任务优先级从批量任务中分离出来, 可以更方便的判断单个任务与批量任务的优先级是否重叠.
- Lanes 使用单个 32 位二进制变量即可代表多个不同的任务,也就是说一个变量即可代表一个组(group)。如果要在一个 group 中分离出单个 task,非常容易。
相关定义见 ReactFiberLane
export const TotalLanes = 31;
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000010;
export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000000100;
export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000001000;
export const DefaultLane: Lane = /* */ 0b0000000000000000000000000010000;
在更新流程中提到的的事件优先级,update 优先级本质都是 Lane 优先级。
事件优先级
开发者在 React 回调中所接触到的事件都是 React 的中的合成事件。当某个事件触发后,React 在调用回调函数之前会设置一个事件优先级。
React 事件优先级分为以下几类:
export const NoEventPriority: EventPriority = NoLane;
export const DiscreteEventPriority: EventPriority = SyncLane;
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
export const DefaultEventPriority: EventPriority = DefaultLane;
export const IdleEventPriority: EventPriority = IdleLane;
- 离散事件(DiscreteEvent):click、keydown、focusin 等,触发不是连续的。
- 连续事件(UserBlockingEvent):drag、scroll、mouseover 等,特点是连续触发。
- …
相关定义见 getEventPriority
事件优先级是在注册阶段被确认的,会在 root 节点上注册。根据事件的类别,创建不同优先级的时间监听。
update 优先级
每当有状态改变,react 会产生一个 update。多个 update 还会形成环状链表。产生的每个 update 具有一个优先级,称为 update 优先级当 update 产生后。此外这个 update 优先级还有以下作用:
- react 会将该 update 优先级递归添加到父 fiber 的 childLanes 中。因此如果每个 fiber 产生了 update,则其父 fiber 的 childLanes 优先级一定大于等于该 update 优先级
- react 会将该 update 的优先级添加到对应 fiber 的优先级中,也就是 fiber.lanes
- react 会将该 update 优先级添加到 root 节点的 pendingLanes 中,表示该优先级对应的 update 有尚未完成的工作
update 优先级的计算方法有以下几种:
- 如果该 update 在合成事件回调中产生,则该 update 优先级等于合成事件的事件优先级
- 如果该 update 在 setTimeout,setInterval, useEffect 等非合成事件回调产生,则 update 优先级等于 DefaultLane
- 如果该 update 在 React.startTransition 回调中产生,那么该 update 优先级会从 TransitionLanes 选取一个产生。
任务优先级
任务优先级是 React 执行更新任务时所持有的优先级,被用来区分多个更新任务的紧急程度。任务优先级保证高优先级任务及时响应,收敛同等优先级的任务调度。
两个任务优先级。如果后者高于前者优先级,就会取消前者的任务调度;如果后者等于前者优先级,会复用前者的更新任务;如果后者低于前者优先级,会等前者更新后再进行任务调度。
相关调度见 ReactFiberRootScheduler
const nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
);
调用 getNextLanes 计算在本次更新中应该处理的这批 lanes(nextLanes)。再调用 getHighestPriorityLanes 去计算任务优先级,得到的任务优先级其实就是 Lane 定义的优先级。
调度优先级
调度优先级由任务优先级计算得出,在 ensureRootIsScheduled 更新真正让 Scheduler 发起调度的时候,会去计算调度优先级。
相关换算见 ReactFiberRootScheduler
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingSchedulerPriority;
break;
case DefaultEventPriority:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
case IdleEventPriority:
schedulerPriorityLevel = IdleSchedulerPriority;
break;
default:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
}
总结
事件优先级、更新优先级、任务优先级、调度优先级,它们之间是递进的关系。事件优先级由事件本身决定,更新优先级由事件计算得出。任务优先级根据优先级计算得到。调度优先级根据任务优先级获取。