# 阅读图解React笔记6
Table of Contents
本文仅仅是阅读 图解 React 原理系列 的笔记,了解更多内容请查看原文链接。
Fiber 架构
React 中有三种节点类型:
- React Element(React 元素),即 createElement 方法的返回值。
- React Component(React 组件),开发者在 React 中定义的函数、类两种组件。
- FiberNode,组成 Fiber 架构的节点类型。
FiberNode
作为一个构造函数,FiberNode 包含很多属性。
// FiberNode 构造函数function FiberNode(tag, pendingProps, key, mode) { this.tag = tag this.key = key this.elementType = null this.type = null this.stateNode = null
// 省略
// Fiber 架构是由多个 FiberNode 组成的树状结构。 // FiberNode 之间的关系是由如下属性连接。 // 指向父 FiberNode this.return = null // 指向第一个子节点 this.child = null // 指向右边的兄弟节点 this.sibling = null}指向父节点的字段采用 return,主要是因为 FiberNode 是一个工作单元,return 是指 FiberNode 执行完后返回下一个 FiberNode。
开发人员通过编程只能控制 ReactElement 树的结构,ReactElement 树驱动 Fiber 树,Fiber 树再驱动 DOM 树,最后展现到页面上。 所以 Fiber 树的构造过程,实际上就是 ReactElement 对象到 Fiber 对象的转换过程。
全局变量
Fiber 树构造循环过程在 ReactFiberWorkLoop.js 中。
在 React 运行中,ReactFiberWorkLoop 闭包中的全局变量会随着 fiber 树构造循环进行变化。
// 当前 React 的执行栈let executionContext: ExecutionContext = NoContext;
// 当前 root 节点let workInProgressRoot: FiberRoot | null = null;// 当前 fiber 节点let workInProgress: Fiber | null = null;// 当前渲染的车道let workInProgressRootRenderLanes: Lanes = NoLanes;
// 防止无限循环和嵌套更新const NESTED_UPDATE_LIMIT = 50;let nestedUpdateCount: number = 0;let rootWithNestedUpdates: FiberRoot | null = null;
const NESTED_PASSIVE_UPDATE_LIMIT = 50;let nestedPassiveUpdateCount: number = 0;
let currentEventTime: number = NoTimestamp;let currentEventWipLanes: Lanes = NoLanes;let currentEventPendingLanes: Lanes = NoLanes;执行上下文
在全局变量有 executionContext,代表渲染期间的执行栈/执行上下文,它也是一个二进制表示的变量,通过位运算进行操作,在源码中一共定义了 8 种执行栈:
type ExecutionContext = number;
export const NoContext = /* */ 0b0000000;const BatchedContext = /* */ 0b0000001;const EventContext = /* */ 0b0000010;const DiscreteEventContext = /* */ 0b0000100;const LegacyUnbatchedContext = /* */ 0b0001000;const RenderContext = /* */ 0b0010000;const CommitContext = /* */ 0b0100000;export const RetryAfterError = /* */ 0b1000000;双缓冲技术
React 中双缓冲技术(double buffering)是指 Fiber 架构同时存在两棵 fiber 树,一棵是真实 UI 对应的 Fiber 树 current,一棵是内存中正在构建的 Fiber 树 workInProgress。
如果没有双缓存技术,那么对于复杂、费时的 Fiber 构造,用户会看到闪烁,不完整的图像。双缓存技术在 workInProgress Fiber 树构建完成,立刻重新渲染页面,切换 current = workInProgress。
// alternate 属性指向缓冲区的对应 FiberNodecurrent.alternate === workInProgressworkInProgress.alternate === current优先级
React 对应优先级可以分为 3 种类型:
- fiber 优先级(LanePriority),管理 Fiber 的优先级
- 调度优先级(SchedulerPriority),管理调度的优先级
- 优先级等级(ReactPriorityLevel),负责上述 2 套优先级体系的转换
而在 fiber 优先级,又可以分为 3 个方面:
- Update 优先级
- 渲染优先级
- fiber 优先级
Update 优先级
正常情况下, 根据当前的调度优先级来生成一个 lane,特殊情况下(处于 suspense 过程中), 会优先选择 TransitionLanes 通道中的空闲通道
这里包含了一个热点问题(setState 到底是同步还是异步)的标准答案:
-
如果逻辑进入 flushSyncCallbackQueue(executionContext === NoContext),则会主动取消调度,并刷新回调,立即进入 fiber 树构造过程,当执行 setState 下一行代码时,fiber 树已经重新渲染了,故 setState 体现为同步。
-
正常情况下,不会取消 schedule 调度,由于 schedule 调度是通过 MessageChannel 触发宏任务,故体现为异位。
渲染优先级
在正式 render 之前,都会调用 getNextLanes 获取一个优先级。
export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes { // 如果没有待处理的工作,提前进行救助 const pendingLanes = root.pendingLanes; if (pendingLanes === NoLanes) { return_highestLanePriority = NoLanePriority; return NoLanes; }
let nextLanes = NoLanes; let nextLanePriority = NoLanePriority;
const expiredLanes = root.expiredLanes; const suspenseLanes = root.suspenseLanes; const pingedLanes = root.pingedLanes;
// 检查是否有工作过期 if (expiredLanes) { nextLanes = expiredLanes; nextLanePriority = return_highestLanePriority = SyncLanePriority; } else { // 在所有非空闲的工作完成之前,不进行任何空闲的工作 const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
if (nonIdlePendingLanes !== NoLanes) { // 非空闲任务... } else { // 空闲任务... } }
if (nextLanes === NoLanes) { return NoLanes; }
// 省略其他代码...
return nextLanes;}getNextLanes 会根据 fiberRoot 对象上的属性(expiredLanes,suspenseLanes,pingedLanes 等),确定出当前最紧急的 lanes。
fiber 优先级
基本概念中介绍过 fiber 对象的数据结构,其中有 2 个属性与优先级相关:
- fiber.lanes:代表本节点的优先级
- fiber.childLanes: 代表子节点的优先级
从 FiberNode 的构造函数中可以看出,fiber.lanes 和 fiber.childLanes 的初始值都为 NoLanes,在 fiber 树构造过程中,使用全局的渲染优先度(renderLanes)和 fiber.lanes 判断 fiber 节点是否更新。
- 如果全局的渲染优先级 renderLanes 不包括 fiber.lanes,证明该 fiber 节点没有更新,可以复用。
- 如果不能复用,进入创建阶段。
挂载
Fiber 树构建主要有两个过程:
- 创建,在首次启动时,界面还没有渲染,直接构造一棵全新的树。
- 更新,发生更新后,创建新的 Fiber 树与之前的 Fiber 树对比。
mount 时 Fiber Tree 的构建过程如下:
- 创建 FiberRootNode。
- 创建 FiberNode,代表 HostRoot,称为 HostRootFiber。
- 从 HostRootFiber 开始,以 DFS(Depth-First-Search,深度优先搜索)的顺序生成 FiberNode。
- 在遍历过程中,为 FiberNode 标记 “代表不同副作用的 flags”,以便后续在 Renderer 中使用。
FiberRootNode 负责管理该应用的全局事务,如:
- Current Fiber 树与 workInProgress Fiber 树之间的切换。
- 应用中任务的过期时间。
- 应用的任务调度信息。
构建过程
从上图可以看出,根据 Scheduler 调度的结果不同,Fiber 构建开始与 performSyncWorkOnRoot(同步更新流程)或 performConcurrentWorkOnRoot(并发更新流程)方法。
最终都会进入到不同 workloop 流程,而两个 workloop 唯一的区别是否调用 shouldYield(是否可中断)。而最终都会进入 performUnitOfWork 的函数。
// unitOfWork 就是被传入 workInProgressfunction performUnitOfWork(unitOfWork: Fiber): void { const current = unitOfWork.alternate; let next;
next = beginWork(current, unitOfWork, subtreeRenderLanes); unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) { // 如果没有新的 FiberNode, 就完成当前的作业 completeUnitOfWork(unitOfWork); } else { workInProgress = next; }}从 performUnitOfWork 函数的实现可以看出,fiber 树的构建是一个深度优先遍历,因此 performUnitOfWork 可以看成两部分:
- beginWork,递的探寻过程
- completeWork,归的回溯过程
beginWork
function beginWork(current, workInProgress, renderLanes) { if (current !== null) { // 判断是否为 update, 省略代码 } else { // mount,省略代码 }
// 最高优先级 workInProgress.lanes = NoLanes
// 根据 tag 不同,进入不同的处理逻辑 switch (workInProgress.tag) { case FunctionComponent: // 省略代码 case ClassComponent: // 省略代码 case HostRoot: // 省略代码 case HostComponent: // 省略代码 case HostText: // 省略代码 //... }}- HostComponent 代表原生 Element 类型(div, span 等)
- HostText 代表文本类型元素
- mountChildFibers 与 reconcileChildFibers 是调用 reconcileChild 的不同返回值
completeWork
completeWork 流程大致包括两个步骤:
- 创建或标记元素更新
- flags 冒泡