React 源码专栏之 React 任务优先级
React 优先级管理是 React 的 Fiber 架构中一个关键部分,它使得可中断渲染、时间切片和异步渲染(Suspense)成为可能。通过优先级管理,React 能够确保高优先级的任务(如用户输入)得到及时响应,从而提供更流畅的用户体验。
不同优先级的任务间,会存在一种现象,当执行低优先级任务时,突然插入一个高优先级任务,那么会中断低优先级的任务,先执行高优先级的任务,我们可以将这种现象称为任务插队。
当高优先级任务执行完,准备执行低优先级任务时,又插入一个高优先级任务,那么又会执行高优先级任务,如果不断有高优先级任务插队执行,那么低优先级任务便一直得不到执行,我们称这种现象为任务饥饿问题。(对于饥饿问题,我们会在后面的内容中讲到如何处理)
当用户操作界面时,例如搜索、点击、下拉等操作,为了避免页面卡顿,需要让出线程的执行权,优先执行用户触发的事件,这个我们称之为高优先级任务,其它不那么重要的事件我们称之为低优先级任务。
优先级分类
在整个 React 内部中对于优先级的管理,根据其功能的不同分为了三种优先级:
-
lane 优先级:
-
SyncLane
:同步优先级,立即执行的更新,例如用户输入。 -
InputContinuousLane
:输入连续性优先级,用于确保输入的连贯性,如拖动操作。 -
DefaultLane
:默认优先级,通常用于正常的状态更新。 -
TransitionLane
:过渡优先级,用于异步更新,如 Suspense 和并发模式中的更新。 -
IdleLane
:空闲优先级,仅在主线程完全空闲时执行。
-
-
React 事件优先级:
-
Discrete Events
:离散事件,例如点击、按键等,这些事件需要立即响应。 -
Continuous Events
:连续事件,例如滚动、鼠标移动等,这些事件需要持续响应。 -
Default Events
:默认事件,例如一些不太紧急的状态更新。
-
-
Schedule 优先级:
-
Immediate Priority
:立即优先级,最高优先级的任务,必须立即执行。 -
User Blocking Priority
:用户阻塞优先级,需要尽快完成以确保用户体验流畅。 -
Normal Priority
:正常优先级,普通的任务,稍微延迟也没关系。 -
Low Priority
:低优先级,可以显著延迟的任务。 -
Idle Priority
:空闲优先级,仅在主线程完全空闲时执行的任务。
-
上面只是列举了一些常见的,还有很多优先级的,具体的会在下面的内容中讲到。
React 中的优先级管理通过 schedule 调度器和 Fiber 架构实现。schedule 调度器负责管理任务的执行顺序,Fiber 架构则用于表示和管理组件树的更新。
lane 优先级
在 React 中,lane 是用来标识更新优先级的位掩码,它可以在频繁运算的时候占用内存少,计算速度快。每个 lane 代表一种优先级级别,React 可以同时处理多个 lane,通过这种方式来管理不同优先级的任务。
回想一下我们学校的操场,是不是分为了多个跑道,跑道越往内,距离越近,lane 模型借助了这个概念,将其分为了 31 个赛道,其中位数越少的赛道,也就是 1 越往右的赛道有优先级 就越高,某些相邻的赛道拥有相同的 优先级,称为赛道组。
在 18 版本中,React 有 31 中 lane,如下代码所示:
export const TotalLanes = 31;
// 表示没有车道,所有为都为0
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;
// 事务注水车道
const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;
// 事务车道 16条
const TransitionLanes: Lanes = /* */ 0b0000000001111111111111111000000;
const TransitionLane1: Lane = /* */ 0b0000000000000000000000001000000;
const TransitionLane2: Lane = /* */ 0b0000000000000000000000010000000;
const TransitionLane3: Lane = /* */ 0b0000000000000000000000100000000;
const TransitionLane4: Lane = /* */ 0b0000000000000000000001000000000;
const TransitionLane5: Lane = /* */ 0b0000000000000000000010000000000;
const TransitionLane6: Lane = /* */ 0b0000000000000000000100000000000;
const TransitionLane7: Lane = /* */ 0b0000000000000000001000000000000;
const TransitionLane8: Lane = /* */ 0b0000000000000000010000000000000;
const TransitionLane9: Lane = /* */ 0b0000000000000000100000000000000;
const TransitionLane10: Lane = /* */ 0b0000000000000001000000000000000;
const TransitionLane11: Lane = /* */ 0b0000000000000010000000000000000;
const TransitionLane12: Lane = /* */ 0b0000000000000100000000000000000;
const TransitionLane13: Lane = /* */ 0b0000000000001000000000000000000;
const TransitionLane14: Lane = /* */ 0b0000000000010000000000000000000;
const TransitionLane15: Lane = /* */ 0b0000000000100000000000000000000;
const TransitionLane16: Lane = /* */ 0b0000000001000000000000000000000;
// 重试车道
const RetryLanes: Lanes = /* */ 0b0000111110000000000000000000000;
const RetryLane1: Lane = /* */ 0b0000000010000000000000000000000;
const RetryLane2: Lane = /* */ 0b0000000100000000000000000000000;
const RetryLane3: Lane = /* */ 0b0000001000000000000000000000000;
const RetryLane4: Lane = /* */ 0b0000010000000000000000000000000;
const RetryLane5: Lane = /* */ 0b0000100000000000000000000000000;
export const SomeRetryLane: Lane = RetryLane1;
// 选着性注水车道
export const SelectiveHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
// 非空闲车道
const NonIdleLanes: Lanes = /* */ 0b0001111111111111111111111111111;
// 空闲注水车道和空闲车道
export const IdleHydrationLane: Lane = /* */ 0b0010000000000000000000000000000;
export const IdleLane: Lane = /* */ 0b0100000000000000000000000000000;
// 离屏渲染车道
export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
如果把所有的 lane 从低位到高位排列到一块,我们就能看到这些数字会从低位到高位依次出现在对应的位置上。
我们来特别关注一下上面优先级中的过渡优先级:
const TransitionLanes: Lanes = /* */ 0b0000000001111111111111111000000;
过渡优先级分配规则是:分配优先级时,会从过渡优先级的最右边开始分配,后续产生的任务则会依次向左移动一位,直到最后一个位置被分配后,后面的任务会从最右边第一个位置再开始做分配
当前产生了一个任务 1
,那么会分配过渡优先级的最右边第一个位置:
TransitionLane1 = 0b0000000000000000000000001000000;
现在又产生了任务 2
,那么则从 A 的位置向左移动一位:
TransitionLane2 = 0b0000000000000000000000010000000;
后续产生的任务则会依次向左移动一位,过渡优先级共有 16 位:
TransitionLanes = 0b0000000001111111111111111000000;
在复杂的 React 应用中,可能同时存在多个过渡任务,这些任务可能属于不同的优先级。通过合并多个 TransitionLane,React 调度器可以灵活地处理这些任务,而不需要分别调度每个优先级的任务。
合并优先级 lane 可以减少调度器的开销。调度器可以一次性检查多个优先级的任务,避免了多次遍历任务队列,从而提高性能。
假设我们有以下几个过渡优先级 lane:
const TransitionLane1 = 0b0000000000000000000000000010000; // 16
const TransitionLane2 = 0b0000000000000000000000000100000; // 32
const TransitionLane3 = 0b0000000000000000000000001000000; // 64
const TransitionLane4 = 0b0000000000000000000000010000000; // 128
每个位表示一个独立的优先级 lane。通过按位或(OR)操作,可以将多个优先级 lane 合并在一起:
const combinedTransitionLanes = TransitionLane1 | TransitionLane2;
combinedTransitionLanes 的值是 0b0000000000000000000000000110000
,表示同时包含 TransitionLane1 和 TransitionLane2。
除此之外,我们可以通过按位与(AND)操作来判断某个任务是否属于某个优先级 lane,并通过按位与非(AND NOT��操作来删除某个优先级 lane。
const TransitionLane1 = 0b0000000000000000000000000010000; // 16
const TransitionLane2 = 0b0000000000000000000000000100000; // 32
const TransitionLane3 = 0b0000000000000000000000001000000; // 64
const TransitionLane4 = 0b0000000000000000000000010000000; // 128
const TransitionLanes = 0b0000000001111111111111111000000;
// 判断某个任务是否属于任务组
const isInTransitionLanes1 = (TransitionLanes & TransitionLane1) !== 0;
console.log(`TransitionLane1 属于任务组: ${isInTransitionLanes1}`); // 输出 true 或 false
const isInTransitionLanes2 = (TransitionLanes & TransitionLane2) !== 0;
console.log(`TransitionLane2 属于任务组: ${isInTransitionLanes2}`); // 输出 true 或 false
const isInTransitionLanes3 = (TransitionLanes & TransitionLane3) !== 0;
console.log(`TransitionLane3 属于任务组: ${isInTransitionLanes3}`); // 输出 true 或 false
const isInTransitionLanes4 = (TransitionLanes & TransitionLane4) !== 0;
console.log(`TransitionLane4 属于任务组: ${isInTransitionLanes4}`); // 输出 true 或 false
// 删除某个过渡任务
const removedTransitionLane1 = TransitionLanes & ~TransitionLane1;
console.log(
`删除 TransitionLane1 后的任务组: ${removedTransitionLane1.toString(2)}`
);
const removedTransitionLane2 = TransitionLanes & ~TransitionLane2;
console.log(
`删除 TransitionLane2 后的任务组: ${removedTransitionLane2.toString(2)}`
);
lane 的基本运算就是一些基础的集合运行,主要有以下这些函数:
// src/react/packages/react-reconciler/src/ReactFiberLane.old.js
// 获取指定 Lanes 中的任意一个 Lane 的索引
function pickArbitraryLaneIndex(lanes: Lanes) {
return 31 - clz32(lanes);
}
// 将单个 Lane 转换为其索引
function laneToIndex(lane: Lane) {
return pickArbitraryLaneIndex(lane);
}
// 判断两个 lanes 集合之间是否有交集
export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane) {
return (a & b) !== NoLanes;
}
// 判断两个 lanes 集合是否是父子集的关系
export function isSubsetOfLanes(set: Lanes, subset: Lanes | Lane) {
return (set & subset) === subset;
}
// 合并两个 lanes 集合
export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
return a | b;
}
// 从一个 lanes 集合中把某个 lane 或者 lanes 集合给移除掉,相当于求一个集合的补集
export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {
return set & ~subset;
}
// 求两个 lanes 集合的交集
export function intersectLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
return a & b;
}
React 事件优先级
React 事件优先级(Event Priorities)是 React 调度机制的一部分,用于管理和协调用户事件的处理顺序。通过为不同类型的事件分配不同的优先级,React 可以确保关键事件得到及时处理,而低优先级的事件可以在不影响用户体验的情况下延迟处理。 React 事件优先级主要分为以下几类:
-
Discrete Events(离散事件):立即响应的用户交互事件,例如点击、按键。这些事件需要快速响应,不能被延迟处理。
-
Continuous Events(连续事件):需要持续响应的事件,例如滚动、拖动。这些事件可以被分段处理,确保流畅的用户体验。
-
Default Events(默认事件):常规优先级的事件,不需要立即响应。例如数据加载、页面渲染。
-
Idle Events(空闲事件):仅在浏览器空闲时执行的事件。例如后台数据同步。
// src/react/packages/react-reconciler/src/ReactEventPriorities.old.js
// 离散事件优先级,例如:点击事件,input输入等触发的更新任务,优先级最高
export const DiscreteEventPriority: EventPriority = SyncLane;
// 连续事件优先级,例如:滚动事件,拖动事件等,连续触发的事件
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
// 默认事件优先级,例如:setTimeout触发的更新任务
export const DefaultEventPriority: EventPriority = DefaultLane;
// 闲置事件优先级,优先级最低
export const IdleEventPriority: EventPriority = IdleLane;
Schedule 优先级
React 的调度机制使用了一种称为“优先级 lane”的机制来管理任务的优先级。Schedule 调度器会根据不同的优先级来决定任务的执行顺序和时间。
在 React Scheduler 中,有六种主要的优先级类型,每种类型对应一个整数值:
-
NoPriority (0):无优先级,表示不需要立即执行的任务。
-
ImmediatePriority (1):立即优先级,表示必须立即执行的任务,不能被打断。
-
UserBlockingPriority (2):用户阻塞优先级,表示需要快速响应但可以被更高优先级任务打断的任务。
-
NormalPriority (3):正常优先级,表示一般的任务,可以被更高优先级的任务打断。
-
LowPriority (4):低优先级,表示不紧急的任务,可以在空闲时间处理。
-
IdlePriority (5):空闲优先级,仅在浏览器空闲时执行的任务。
这些优先级用于 React 调度器决定哪些任务应该首先执行,哪些任务可以延迟执行。
// 无优先级
export const NoPriority = 0;
// 立即执行优先级
export const ImmediatePriority = 1;
// 用户阻塞操作优先级 点击,输入
export const UserBlockingPriority = 2;
// 正常优先级
export const NormalPriority = 3;
// 低优先级
export const LowPriority = 4;
// 空闲优先级
export const IdlePriority = 5;
优先级转换关系
在整个 React 应用当中,它们 key 分为四种优先级,它们分别有如下优先级:
-
事件优先级: 按照用户事件的交互紧急程度,划分的优先级;
-
更新优先级:事件导致 React 产生的更新对象 update 的优先级;
-
任务优先级:产生更新对象之后,React 去执行一个更新任务,这个任务所持有的优先级;
-
调度优先级: Schedule 依据 React 更新任务生成一个调度任务,这个调度任务所持有的优先级;
前三者属于 React 的优先级机制,第四个属于 Scheduler 的优先级机制,Scheduler 内部有自己的优先级机制,虽然与 React 有所区别,但等级的划分基本一致。
Lane 优先级转换为 React 事件优先级如下代码所示:
// src/react/packages/react-reconciler/src/ReactEventPriorities.old.js
// lanes模型优先级转换为事件优先级
export function lanesToEventPriority(lanes: Lanes): EventPriority {
const lane = getHighestPriorityLane(lanes);
if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
return DiscreteEventPriority;
}
if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
return ContinuousEventPriority;
}
if (includesNonIdleWork(lane)) {
return DefaultEventPriority;
}
return IdleEventPriority;
}
React 事件优先级转换为 Scheduler 优先级如下代码所示:
它主要是在 src/react/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
文件中的 ensureRootIsScheduled
函数。
lanesToEventPriority 函数就是上面 Lane 优先级转换为 React 事件优先级的函数,先将 lane 的优先级转换为 React 事件的优先级,然后再根据 React 事件的优先级转换为 Scheduler 的优先级。
总结
通过本文的学习我们应该对 React 的任务优先级有一个简单的了解了,以及 lane 模型采用位运算的方式取代了之前的 expirationTime 模型,减少了内存的占用,而从运算的性能来看,按位运算肯定是高效于普通的算术运算。
正是通过优先级的灵活运用, React 实现了可中断渲染,时间切片(time slicing),异步渲染(suspense)等特性.
更多的优势将会从后面的内容中更好地体现出来。虽然本篇文章内容结束了,但是任务优先级这里还远远没有技术,这里还设计了 schedule 调度引起的饥饿问题,还有 lane 模型的生命周期,这些都将会从后面的内容或者单独一篇的内容提炼出来。
转载自:https://juejin.cn/post/7394284803749101568