图解React图解React
  • 原理解析
  • 高频算法
  • 面试题
⌘ K
基本概念
宏观包结构
两大工作循环
高频对象
运行核心
启动过程
reconciler 运作流程
优先级管理
调度原理
fiber 树构造(基础准备)
fiber 树构造(初次创建)
fiber 树构造(对比更新)
fiber 树渲染
状态管理
状态与副作用
Hook 原理(概览)
Hook 原理(状态Hook)
Hook 原理(副作用Hook)
context 原理
交互
合成事件
Copyright © 2023 | Powered by dumi

React 中的优先级管理

React内部对于优先级的管理, 根据功能的不同分为LanePriority, SchedulerPriority, ReactPriorityLevel3 种类型. 本文基于[email protected], 梳理源码中的优先级管理体系.

React是一个声明式, 高效且灵活的用于构建用户界面的 JavaScript 库. React 团队一直致力于实现高效渲染, 其中有 2 个十分有名的演讲:

  1. 2017 年 Lin Clark 的演讲中介绍了fiber架构和可中断渲染.
  2. 2018 年 Dan 在 JSConf 冰岛的演讲进一步介绍了时间切片(time slicing)和异步渲染(suspense)等特性.

演讲中所展示的可中断渲染,时间切片(time slicing),异步渲染(suspense)等特性, 在源码中得以实现都依赖于优先级管理.

在[email protected]源码中, 一共有2套优先级体系和1套转换体系, 在深入分析之前, 再次回顾一下(reconciler 运作流程):

React内部对于优先级的管理, 贯穿运作流程的 4 个阶段(从输入到输出), 根据其功能的不同, 可以分为 3 种类型:

  1. fiber优先级(LanePriority): 位于react-reconciler包, 也就是Lane(车道模型).
  2. 调度优先级(SchedulerPriority): 位于scheduler包.
  3. 优先级等级(ReactPriorityLevel) : 位于react-reconciler包中的SchedulerWithReactIntegration.js, 负责上述 2 套优先级体系的转换.

预备知识

在深入分析 3 种优先级之前, 为了深入理解LanePriority, 需要先了解Lane, 这是[email protected]的新特性.

Lane (车道模型)

英文单词lane翻译成中文表示"车道, 航道"的意思, 所以很多文章都将Lanes模型称为车道模型

Lane模型的源码在ReactFiberLane.js, 源码中大量使用了位运算(有关位运算的讲解, 可以参考React 算法之位运算).

首先引入作者对Lane的解释(相应的 pr), 这里简单概括如下:

  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模型的优势:

    1. 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. 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
      //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;

      通过上述伪代码, 可以看到Lanes的优越性, 运用起来代码量少, 简洁高效.

  4. Lanes是一个不透明的类型, 只能在ReactFiberLane.js这个模块中维护. 如果要在其他文件中使用, 只能通过ReactFiberLane.js中提供的工具函数来使用.

分析车道模型的源码(ReactFiberLane.js中), 可以得到如下结论:

  1. 可以使用的比特位一共有 31 位(为什么? 可以参考React 算法之位运算中的说明).
  2. 共定义了18 种车道(Lane/Lanes)变量, 每一个变量占有 1 个或多个比特位, 分别定义为Lane和Lanes类型.
  3. 每一种车道(Lane/Lanes)都有对应的优先级, 所以源码中定义了 18 种优先级(LanePriority).
  4. 占有低位比特位的Lane变量对应的优先级越高
    • 最高优先级为SyncLanePriority对应的车道为SyncLane = 0b0000000000000000000000000000001.
    • 最低优先级为OffscreenLanePriority对应的车道为OffscreenLane = 0b1000000000000000000000000000000.

优先级区别和联系

在源码中, 3 种优先级位于不同的 js 文件, 是相互独立的.

注意:

  • LanePriority和SchedulerPriority从命名上看, 它们代表的是优先级
  • ReactPriorityLevel从命名上看, 它代表的是等级而不是优先级, 它用于衡量LanePriority和SchedulerPriority的等级.

LanePriority

LanePriority: 属于react-reconciler包, 定义于ReactFiberLane.js(见源码).

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;

与fiber构造过程相关的优先级(如fiber.updateQueue,fiber.lanes)都使用LanePriority.

由于本节重点介绍优先级体系以及它们的转换关系, 关于Lane(车道模型)在fiber树构造时的具体使用, 在fiber 树构造章节详细解读.

SchedulerPriority

SchedulerPriority, 属于scheduler包, 定义于SchedulerPriorities.js中(见源码).

export const NoPriority = 0;
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;

与scheduler调度中心相关的优先级使用SchedulerPriority.

ReactPriorityLevel

reactPriorityLevel, 属于react-reconciler包, 定义于SchedulerWithReactIntegration.js中(见源码).

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;

LanePriority与SchedulerPriority通过ReactPriorityLevel进行转换

转换关系

为了能协同调度中心(scheduler包)和 fiber 树构造(react-reconciler包)中对优先级的使用, 则需要转换SchedulerPriority和LanePriority, 转换的桥梁正是ReactPriorityLevel.

在SchedulerWithReactIntegration.js中, 可以互转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.');
}
}

在ReactFiberLane.js中, 可以互转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,
);
}
}

优先级使用

通过reconciler 运作流程中的归纳, reconciler从输入到输出一共经历了 4 个阶段, 在每个阶段中都会涉及到与优先级相关的处理. 正是通过优先级的灵活运用, React实现了可中断渲染,时间切片(time slicing),异步渲染(suspense)等特性.

在理解了优先级的基本思路之后, 接下来就正式进入 react 源码分析中的硬核部分(scheduler 调度原理和fiber树构造)

总结

本文介绍了 react 源码中有关优先级的部分, 并梳理了 3 种优先级之间的区别和联系. 它们贯穿了reconciler 运作流程中的 4 个阶段, 在 react 源码中所占用的代码量比较高, 理解它们的设计思路, 为接下来分析调度原理和fiber构造打下基础.