Reconciler 工作原理
react-reconciler 的核心机制原理是
输入:暴露 api 的实现,提供给其他包进行调度
注册调度任务:和调度中心 scheduler 包进行交互实现,注册调度任务 task,等待任务回调
执行任务回调:和内存中的构造出的 fiber 树,同时和渲染器 react-dom 进行交互,在内存中创建吃 fiber 对象的 DOM 节点
输出:输出和渲染器 react-dom 交互,渲染 DOM 节点吧

输入
// 唯一接收输入信号的函数
export function scheduleUpdateOnFiber(
fiber: Fiber,
lane: Lane,
eventTime: number,
) {
// ... 省略部分无关代码
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
if (lane === SyncLane) {
if (
(executionContext & LegacyUnbatchedContext) !== NoContext &&
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// 直接进行`fiber构造`
performSyncWorkOnRoot(root);
} else {
// 注册调度任务, 经过`Scheduler`包的调度, 间接进行`fiber构造`
ensureRootIsScheduled(root, eventTime);
}
} else {
// 注册调度任务, 经过`Scheduler`包的调度, 间接进行`fiber构造`
ensureRootIsScheduled(root, eventTime);
}
}逻辑进入到scheduleUpdateOnFiber之后, 后面有 2 种可能:
不经过调度, 直接进行
fiber构造.注册调度任务, 经过
Scheduler包的调度, 间接进行fiber构造.
注册调度任务
// ... 省略部分无关代码
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
// 前半部分: 判断是否需要注册新的调度
const existingCallbackNode = root.callbackNode;
const nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
const newCallbackPriority = returnNextLanesPriority();
if (nextLanes === NoLanes) {
return;
}
if (existingCallbackNode !== null) {
const existingCallbackPriority = root.callbackPriority;
if (existingCallbackPriority === newCallbackPriority) {
return;
}
cancelCallback(existingCallbackNode);
}
// 后半部分: 注册调度任务
let newCallbackNode;
if (newCallbackPriority === SyncLanePriority) {
newCallbackNode = scheduleSyncCallback(
performSyncWorkOnRoot.bind(null, root),
);
} else if (newCallbackPriority === SyncBatchedLanePriority) {
newCallbackNode = scheduleCallback(
ImmediateSchedulerPriority,
performSyncWorkOnRoot.bind(null, root),
);
} else {
const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
newCallbackPriority,
);
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
);
}
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}ensureRootIsScheduled的逻辑很清晰, 分为 2 部分:
前半部分: 判断是否需要注册新的调度(如果无需新的调度, 会退出函数)
后半部分: 注册调度任务
performSyncWorkOnRoot或performConcurrentWorkOnRoot被封装到了任务回调(scheduleCallback)中等待调度中心执行任务, 任务运行其实就是执行
performSyncWorkOnRoot或performConcurrentWorkOnRoot
执行任务回调
// ... 省略部分无关代码
function performSyncWorkOnRoot(root) {
let lanes;
let exitStatus;
lanes = getNextLanes(root, NoLanes);
// 1. fiber树构造
exitStatus = renderRootSync(root, lanes);
// 2. 异常处理: 有可能fiber构造过程中出现异常
if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
// ...
}
// 3. 输出: 渲染fiber树
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
commitRoot(root);
// 退出前再次检测, 是否还有其他更新, 是否需要发起新调度
ensureRootIsScheduled(root, now());
return null;
}performSyncWorkOnRoot的逻辑很清晰, 分为 3 部分:
fiber 树构造
异常处理: 有可能 fiber 构造过程中出现异常
调用输出
// ... 省略部分无关代码
function performConcurrentWorkOnRoot(root) {
const originalCallbackNode = root.callbackNode;
// 1. 刷新pending状态的effects, 有可能某些effect会取消本次任务
const didFlushPassiveEffects = flushPassiveEffects();
if (didFlushPassiveEffects) {
if (root.callbackNode !== originalCallbackNode) {
// 任务被取消, 退出调用
return null;
} else {
// Current task was not canceled. Continue.
}
}
// 2. 获取本次渲染的优先级
let lanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
// 3. 构造fiber树
let exitStatus = renderRootConcurrent(root, lanes);
if (
includesSomeLane(
workInProgressRootIncludedLanes,
workInProgressRootUpdatedLanes,
)
) {
// 如果在render过程中产生了新的update, 且新update的优先级与最初render的优先级有交集
// 那么最初render无效, 丢弃最初render的结果, 等待下一次调度
prepareFreshStack(root, NoLanes);
} else if (exitStatus !== RootIncomplete) {
// 4. 异常处理: 有可能fiber构造过程中出现异常
if (exitStatus === RootErrored) {
// ...
}.
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
// 5. 输出: 渲染fiber树
finishConcurrentRender(root, exitStatus, lanes);
}
// 退出前再次检测, 是否还有其他更新, 是否需要发起新调度
ensureRootIsScheduled(root, now());
if (root.callbackNode === originalCallbackNode) {
// 渲染被阻断, 返回一个新的performConcurrentWorkOnRoot函数, 等待下一次调用
return performConcurrentWorkOnRoot.bind(null, root);
}
return null;
}performConcurrentWorkOnRoot的逻辑与performSyncWorkOnRoot的不同之处在于, 对于可中断渲染的支持:
调用
performConcurrentWorkOnRoot函数时, 首先检查是否处于render过程中, 是否需要恢复上一次渲染.如果本次渲染被中断, 最后返回一个新的 performConcurrentWorkOnRoot 函数, 等待下一次调用.
输出
// ... 省略部分无关代码
function commitRootImpl(root, renderPriorityLevel) {
// 设置局部变量
const finishedWork = root.finishedWork;
const lanes = root.finishedLanes;
// 清空FiberRoot对象上的属性
root.finishedWork = null;
root.finishedLanes = NoLanes;
root.callbackNode = null;
// 提交阶段
let firstEffect = finishedWork.firstEffect;
if (firstEffect !== null) {
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
// 阶段1: dom突变之前
nextEffect = firstEffect;
do {
commitBeforeMutationEffects();
} while (nextEffect !== null);
// 阶段2: dom突变, 界面发生改变
nextEffect = firstEffect;
do {
commitMutationEffects(root, renderPriorityLevel);
} while (nextEffect !== null);
root.current = finishedWork;
// 阶段3: layout阶段, 调用生命周期componentDidUpdate和回调函数等
nextEffect = firstEffect;
do {
commitLayoutEffects(root, lanes);
} while (nextEffect !== null);
nextEffect = null;
executionContext = prevExecutionContext;
}
ensureRootIsScheduled(root, now());
return null;
}在输出阶段,commitRoot的实现逻辑是在commitRootImpl函数中, 其主要逻辑是处理副作用队列, 将最新的 fiber 树结构反映到 DOM 上.
核心逻辑分为 3 个步骤:
commitBeforeMutationEffectsdom 变更之前, 主要处理副作用队列中带有
Snapshot,Passive标记的fiber节点.
commitMutationEffectsdom 变更, 界面得到更新. 主要处理副作用队列中带有
Placement,Update,Deletion,Hydrating标记的fiber节点.
commitLayoutEffectsdom 变更后, 主要处理副作用队列中带有
Update | Callback标记的fiber节点.
React 应用启动过程
模式区分
legacy 模式: ReactDOM.render(<App />, rootNode). 这是当前 React app 使用的方式. 这个模式可能不支持这些新功能(concurrent 支持的所有功能).
// LegacyRoot
ReactDOM.render(<App />, document.getElementById('root'), dom => {}); // 支持callback回调, 参数是一个dom对象Blocking 模式: ReactDOM.createBlockingRoot(rootNode).render(<App />). 目前正在实验中, 它仅提供了 concurrent 模式的小部分功能, 作为迁移到 concurrent 模式的第一个步骤.
// BlockingRoot
// 1. 创建ReactDOMRoot对象
const reactDOMBlockingRoot = ReactDOM.createBlockingRoot(
document.getElementById('root'),
);
// 2. 调用render
reactDOMBlockingRoot.render(<App />); // 不支持回调Concurrent 模式: ReactDOM.createRoot(rootNode).render(<App />). 目前在实验中, 未来稳定之后,打算作为 React 的默认开发模式. 这个模式开启了所有的新功能.
// ConcurrentRoot
// 1. 创建ReactDOMRoot对象
const reactDOMRoot = ReactDOM.createRoot(document.getElementById('root'));
// 2. 调用render
reactDOMRoot.render(<App />); // 不支持回调启动流程
创建全局对象(#create-global-obj)
无论Legacy, Concurrent或Blocking模式, react 在初始化时, 都会创建 3 个全局对象
ReactDOM(Blocking)Root对象
fiberRoot对象属于
react-reconciler包, 作为react-reconciler在运行过程中的全局上下文, 保存 fiber 构建过程中所依赖的全局状态.其大部分实例变量用来存储
fiber 构造循环(详见两大工作循环)过程的各种状态.react 应用内部, 可以根据这些实例变量的值, 控制执行逻辑.
HostRootFiber对象属于
react-reconciler包, 这是 react 应用中的第一个 Fiber 对象, 是 Fiber 树的根节点, 节点的类型是HostRoot.
这 3 个对象是 react 体系得以运行的基本保障, 一经创建大多数场景不会再销毁(除非卸载整个应用root.unmount()).
这一过程是从react-dom包发起, 内部调用了react-reconciler包, 核心流程图如下(其中红色标注了 3 个对象的创建时机).

创建ReactDOM(Blocking)Root 对象
由于 3 种模式启动的 api 有所不同, 所以从源码上追踪, 也对应了 3 种方式. 最终都 new 一个ReactDOMRoot或ReactDOMBlockingRoot的实例, 需要创建过程中RootTag参数, 3 种模式各不相同. 该RootTag的类型决定了整个 react 应用是否支持可中断渲染
React 调度原理(scheduler)
在 React 运行时中, 调度中心(位于scheduler包), 是整个 React 运行时的中枢(其实是心脏), 所以理解scheduler调度, 就基本把握了 React 的命门.
在深入分析之前, 建议回顾一下往期与scheduler相关的文章(这 3 篇文章不长, 共 10 分钟能浏览完):
React 工作循环: 从宏观的角度介绍 React 体系中两个重要的循环, 其中
任务调度循环就是本文的主角.reconciler 运作流程: 从宏观的角度介绍了
react-reconciler包的核心作用, 并把reconciler分为了 4 个阶段. 其中第 2 个阶段注册调度任务串联了scheduler包和react-reconciler包, 其实就是任务调度循环中的一个任务(task).React 中的优先级管理: 介绍了 React 体系中的 3 中优先级的管理, 列出了源码中
react-reconciler与scheduler包中关于优先级的转换思路. 其中SchedulerPriority控制任务调度循环中循环的顺序.
内核
export let requestHostCallback; // 请求及时回调: port.postMessage
export let cancelHostCallback; // 取消及时回调: scheduledHostCallback = null
export let requestHostTimeout; // 请求延时回调: setTimeout
export let cancelHostTimeout; // 取消延时回调: cancelTimeout
export let shouldYieldToHost; // 是否让出主线程(currentTime >= deadline && needsPaint): 让浏览器能够执行更高优先级的任务(如ui绘制, 用户输入等)
export let requestPaint; // 请求绘制: 设置 needsPaint = true
export let getCurrentTime; // 获取当前时间
export let forceFrameRate; // 强制设置 yieldInterval (让出主线程的周期). 这个函数虽然存在, 但是从源码来看, 几乎没有用到我们知道 react 可以在 nodejs 环境中使用, 所以在不同的 js 执行环境中, 这些函数的实现会有区别. 下面基于普通浏览器环境, 对这 8 个函数逐一分析 :
调度相关: 请求或取消调度
// 接收 MessageChannel 消息
const performWorkUntilDeadline = () => {
// ...省略无关代码
if (scheduledHostCallback !== null) {
const currentTime = getCurrentTime();
// 更新deadline
deadline = currentTime + yieldInterval;
// 执行callback
scheduledHostCallback(hasTimeRemaining, currentTime);
} else {
isMessageLoopRunning = false;
}
};
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
// 请求回调
requestHostCallback = function(callback) {
// 1. 保存callback
scheduledHostCallback = callback;
if (!isMessageLoopRunning) {
isMessageLoopRunning = true;
// 2. 通过 MessageChannel 发送消息
port.postMessage(null);
}
};
// 取消回调
cancelHostCallback = function() {
scheduledHostCallback = null;
};此处需要注意: MessageChannel在浏览器事件循环中属于宏任务, 所以调度中心永远是异步执行回调函数.
时间切片(
time slicing)相关: 执行时间分割, 让出主线程(把控制权归还浏览器, 浏览器可以处理用户输入, UI 绘制等紧急任务).
getCurrentTime: 获取当前时间
shouldYieldToHost: 是否让出主线程
requestPaint: 请求绘制
forceFrameRate: 强制设置
yieldInterval(从源码中的引用来看, 算一个保留函数, 其他地方没有用到)
const localPerformance = performance;
// 获取当前时间
getCurrentTime = () => localPerformance.now();
// 时间切片周期, 默认是5ms(如果一个task运行超过该周期, 下一个task执行之前, 会把控制权归还浏览器)
let yieldInterval = 5;
let deadline = 0;
const maxYieldInterval = 300;
let needsPaint = false;
const scheduling = navigator.scheduling;
// 是否让出主线程
shouldYieldToHost = function() {
const currentTime = getCurrentTime();
if (currentTime >= deadline) {
if (needsPaint || scheduling.isInputPending()) {
// There is either a pending paint or a pending input.
return true;
}
// There's no pending input. Only yield if we've reached the max
// yield interval.
return currentTime >= maxYieldInterval; // 在持续运行的react应用中, currentTime肯定大于300ms, 这个判断只在初始化过程中才有可能返回false
} else {
// There's still time left in the frame.
return false;
}
};
// 请求绘制
requestPaint = function() {
needsPaint = true;
};
// 设置时间切片的周期
forceFrameRate = function(fps) {
if (fps < 0 || fps > 125) {
// Using console['error'] to evade Babel and ESLint
console['error'](
'forceFrameRate takes a positive int between 0 and 125, ' +
'forcing frame rates higher than 125 fps is not supported',
);
return;
}
if (fps > 0) {
yieldInterval = Math.floor(1000 / fps);
} else {
// reset the framerate
yieldInterval = 5;
}
};注意shouldYieldToHost的判定条件:
currentTime >= deadline: 只有时间超过deadline之后才会让出主线程(其中deadline = currentTime + yieldInterval).yieldInterval默认是5ms, 只能通过forceFrameRate函数来修改(事实上在 v17.0.2 源码中, 并没有使用到该函数).如果一个
task运行时间超过5ms, 下一个task执行之前, 会把控制权归还浏览器.
navigator.scheduling.isInputPending(): 这 facebook 官方贡献给 Chromium 的 api, 现在已经列入 W3C 标准(具体解释), 用于判断是否有输入事件(包括: input 框输入事件, 点击事件等).
const performWorkUntilDeadline = () => {
if (scheduledHostCallback !== null) {
const currentTime = getCurrentTime(); // 1. 获取当前时间
deadline = currentTime + yieldInterval; // 2. 设置deadline
const hasTimeRemaining = true;
try {
// 3. 执行回调, 返回是否有还有剩余任务
const hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
if (!hasMoreWork) {
// 没有剩余任务, 退出
isMessageLoopRunning = false;
scheduledHostCallback = null;
} else {
port.postMessage(null); // 有剩余任务, 发起新的调度
}
} catch (error) {
port.postMessage(null); // 如有异常, 重新发起调度
throw error;
}
} else {
isMessageLoopRunning = false;
}
needsPaint = false; // 重置开关
};
任务队列管理
// Tasks are stored on a min heap
var taskQueue = [];
var timerQueue = [];taskQueue是一个小顶堆数组, 关于堆排序的详细解释, 可以查看React 算法之堆排序.源码中除了
taskQueue队列之外还有一个timerQueue队列. 这个队列是预留给延时任务使用的
创建任务
// 省略部分无关代码
function unstable_scheduleCallback(priorityLevel, callback, options) {
// 1. 获取当前时间
var currentTime = getCurrentTime();
var startTime;
if (typeof options === 'object' && options !== null) {
// 从函数调用关系来看, 在v17.0.2中,所有调用 unstable_scheduleCallback 都未传入options
// 所以省略延时任务相关的代码
} else {
startTime = currentTime;
}
// 2. 根据传入的优先级, 设置任务的过期时间 expirationTime
var timeout;
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT;
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
break;
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT;
break;
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT;
break;
case NormalPriority:
default:
timeout = NORMAL_PRIORITY_TIMEOUT;
break;
}
var expirationTime = startTime + timeout;
// 3. 创建新任务
var newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
if (startTime > currentTime) {
// 省略无关代码 v17.0.2中不会使用
} else {
newTask.sortIndex = expirationTime;
// 4. 加入任务队列
push(taskQueue, newTask);
// 5. 请求调度
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
}
return newTask;
}var newTask = {
id: taskIdCounter++, // id: 一个自增编号
callback, // callback: 传入的回调函数
priorityLevel, // priorityLevel: 优先级等级
startTime, // startTime: 创建task时的当前时间
expirationTime, // expirationTime: task的过期时间, 优先级越高 expirationTime = startTime + timeout 越小
sortIndex: -1,
};
newTask.sortIndex = expirationTime; // sortIndex: 排序索引, 全等于过期时间. 保证过期时间越小, 越紧急的任务排在最前面消费任务
创建任务之后, 最后请求调度requestHostCallback(flushWork)(创建任务源码中的第 5 步), flushWork函数作为参数被传入调度中心内核等待回调. requestHostCallback函数在上文调度内核中已经介绍过了, 在调度中心中, 只需下一个事件循环就会执行回调, 最终执行flushWork.
// 省略无关代码
function flushWork(hasTimeRemaining, initialTime) {
// 1. 做好全局标记, 表示现在已经进入调度阶段
isHostCallbackScheduled = false;
isPerformingWork = true;
const previousPriorityLevel = currentPriorityLevel;
try {
// 2. 循环消费队列
return workLoop(hasTimeRemaining, initialTime);
} finally {
// 3. 还原全局标记
currentTask = null;
currentPriorityLevel = previousPriorityLevel;
isPerformingWork = false;
}
}flushWork中调用了workLoop. 队列消费的主要逻辑是在workLoop函数中, 这就是React 工作循环一文中提到的任务调度循环.
// 省略部分无关代码
function workLoop(hasTimeRemaining, initialTime) {
let currentTime = initialTime; // 保存当前时间, 用于判断任务是否过期
currentTask = peek(taskQueue); // 获取队列中的第一个任务
while (currentTask !== null) {
if (
currentTask.expirationTime > currentTime &&
(!hasTimeRemaining || shouldYieldToHost())
) {
// 虽然currentTask没有过期, 但是执行时间超过了限制(毕竟只有5ms, shouldYieldToHost()返回true). 停止继续执行, 让出主线程
break;
}
const callback = currentTask.callback;
if (typeof callback === 'function') {
currentTask.callback = null;
currentPriorityLevel = currentTask.priorityLevel;
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
// 执行回调
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
// 回调完成, 判断是否还有连续(派生)回调
if (typeof continuationCallback === 'function') {
// 产生了连续回调(如fiber树太大, 出现了中断渲染), 保留currentTask
currentTask.callback = continuationCallback;
} else {
// 把currentTask移出队列
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
}
} else {
// 如果任务被取消(这时currentTask.callback = null), 将其移出队列
pop(taskQueue);
}
// 更新currentTask
currentTask = peek(taskQueue);
}
if (currentTask !== null) {
return true; // 如果task队列没有清空, 返回true. 等待调度中心下一次回调
} else {
return false; // task队列已经清空, 返回false.
}
}workLoop就是一个大循环, 虽然代码也不多, 但是非常精髓, 在此处实现了时间切片(time slicing)和fiber树的可中断渲染. 这 2 大特性的实现, 都集中于这个while循环.
每一次while循环的退出就是一个时间切片, 深入分析while循环的退出条件:
队列被完全清空: 这种情况就是很正常的情况, 一气呵成, 没有遇到任何阻碍.
执行超时: 在消费
taskQueue时, 在执行task.callback之前, 都会检测是否超时, 所以超时检测是以task为单位.如果某个
task.callback执行时间太长(如:fiber树很大, 或逻辑很重)也会造成超时所以在执行
task.callback过程中, 也需要一种机制检测是否超时, 如果超时了就立刻暂停task.callback的执行.
时间切片原理
消费任务队列的过程中, 可以消费1~n个 task, 甚至清空整个 queue. 但是在每一次具体执行task.callback之前都要进行超时检测, 如果超时可以立即退出循环并等待下一次调用.
可中断渲染
在时间切片的基础之上, 如果单个task.callback执行时间就很长(假设 200ms). 就需要task.callback自己能够检测是否超时, 所以在 fiber 树构造过程中, 每构造完成一个单元, 都会检测一次超时(源码链接), 如遇超时就退出fiber树构造循环, 并返回一个新的回调函数(就是此处的continuationCallback)并等待下一次回调继续未完成的fiber树构造.
Fiber 树构造
ReactElement、Fiber、DOM三者关系
ReactElement 对象(type 定义在shared 包中)
所有采用
jsx语法书写的节点, 都会被编译器转换, 最终会以React.createElement(...)的方式, 创建出来一个与之对应的ReactElement对象
fiber 对象(type 类型的定义在ReactInternalTypes.js中)
fiber对象是通过ReactElement对象进行创建的, 多个fiber对象构成了一棵fiber树,fiber树是构造DOM树的数据模型,fiber树的任何改动, 最后都体现到DOM树.
DOM 对象: 文档对象模型
DOM将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合, 也就是常说的DOM树.JavaScript可以访问和操作存储在 DOM 中的内容, 也就是操作DOM对象, 进而触发 UI 渲染.

开发人员能够控制的是
JSX, 也就是ReactElement对象.fiber树是通过ReactElement生成的, 如果脱离了ReactElement,fiber树也无从谈起. 所以是ReactElement树(不是严格的树结构, 为了方便也称为树)驱动fiber树.fiber树是DOM树的数据模型,fiber树驱动DOM树
开发人员通过编程只能控制ReactElement树的结构, ReactElement树驱动fiber树, fiber树再驱动DOM树, 最后展现到页面上. 所以fiber树的构造过程, 实际上就是ReactElement对象到fiber对象的转换过程.
全局变量
// 当前React的执行栈(执行上下文)
let executionContext: ExecutionContext = NoContext;
// 当前root节点
let workInProgressRoot: FiberRoot | null = null;
// 正在处理中的fiber节点
let workInProgress: Fiber | null = null;
// 正在渲染的车道(复数)
let workInProgressRootRenderLanes: Lanes = NoLanes;
// 包含所有子节点的优先级, 是workInProgressRootRenderLanes的超集
// 大多数情况下: 在工作循环整体层面会使用workInProgressRootRenderLanes, 在begin/complete阶段层面会使用 subtreeRenderLanes
let subtreeRenderLanes: Lanes = NoLanes;
// 一个栈结构: 专门存储当前节点的 subtreeRenderLanes
const subtreeRenderLanesCursor: StackCursor<Lanes> = createCursor(NoLanes);
// fiber构造完后, root节点的状态: completed, errored, suspended等
let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;
// 重大错误
let workInProgressRootFatalError: mixed = null;
// 整个render期间所使用到的所有lanes
let workInProgressRootIncludedLanes: Lanes = NoLanes;
// 在render期间被跳过(由于优先级不够)的lanes: 只包括未处理的updates, 不包括被复用的fiber节点
let workInProgressRootSkippedLanes: Lanes = NoLanes;
// 在render期间被修改过的lanes
let workInProgressRootUpdatedLanes: 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;执行上下文
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;双缓冲技术
在全局变量中有workInProgress, 还有不少以workInProgress来命名的变量. workInProgress的应用实际上就是React的双缓冲技术(double buffering).
在上文我们梳理了ReactElement, Fiber, DOM三者的关系, fiber树的构造过程, 就是把ReactElement转换成fiber树的过程. 在这个过程中, 内存里会同时存在 2 棵fiber树:
其一: 代表当前界面的
fiber树(已经被展示出来, 挂载到fiberRoot.current上). 如果是初次构造(初始化渲染), 页面还没有渲染, 此时界面对应的 fiber 树为空(fiberRoot.current = null).其二: 正在构造的
fiber树(即将展示出来, 挂载到HostRootFiber.alternate上, 正在构造的节点称为workInProgress). 当构造完成之后, 重新渲染页面, 最后切换fiberRoot.current = workInProgress, 使得fiberRoot.current重新指向代表当前界面的fiber树.
构造过程中,
fiberRoot.current指向当前界面对应的fiber树.

构造完成并渲染, 切换
fiberRoot.current指针, 使其继续指向当前界面对应的fiber树(原来代表界面的 fiber 树, 变成了内存中).

优先级(lanes)
在全局变量中有不少变量都以 Lanes 命名(如workInProgressRootRenderLanes,subtreeRenderLanes其作用见上文注释), 它们都与优先级相关.
在前文React 中的优先级管理中, 我们介绍了React中有 3 套优先级体系, 并了解了它们之间的关联. 现在fiber树构造过程中, 将要深入分析车道模型Lane的具体应用.
在整个react-reconciler包中, Lane的应用可以分为 3 个方面:
update 优先级
export function createUpdate(eventTime: number, lane: Lane): Update<*> {
const update: Update<*> = {
eventTime,
lane,
tag: UpdateState,
payload: null,
callback: null,
next: null,
};
return update;
}应用初始化: 在react-reconciler包中的updateContainer函数中
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): Lane {
const current = container.current;
const eventTime = requestEventTime();
const lane = requestUpdateLane(current); // 根据当前时间, 创建一个update优先级
const update = createUpdate(eventTime, lane); // lane被用于创建update对象
update.payload = { element };
enqueueUpdate(current, update);
scheduleUpdateOnFiber(current, lane, eventTime);
return lane;
}const classComponentUpdater = {
isMounted,
enqueueSetState(inst, payload, callback) {
const fiber = getInstance(inst);
const eventTime = requestEventTime(); // 根据当前时间, 创建一个update优先级
const lane = requestUpdateLane(fiber); // lane被用于创建update对象
const update = createUpdate(eventTime, lane);
update.payload = payload;
enqueueUpdate(fiber, update);
scheduleUpdateOnFiber(fiber, lane, eventTime);
},
};export function requestUpdateLane(fiber: Fiber): Lane {
// Special cases
const mode = fiber.mode;
if ((mode & BlockingMode) === NoMode) {
// legacy 模式
return (SyncLane: Lane);
} else if ((mode & ConcurrentMode) === NoMode) {
// blocking模式
return getCurrentPriorityLevel() === ImmediateSchedulerPriority
? (SyncLane: Lane)
: (SyncBatchedLane: Lane);
}
// concurrent模式
if (currentEventWipLanes === NoLanes) {
currentEventWipLanes = workInProgressRootIncludedLanes;
}
const isTransition = requestCurrentTransition() !== NoTransition;
if (isTransition) {
// 特殊情况, 处于suspense过程中
if (currentEventPendingLanes !== NoLanes) {
currentEventPendingLanes =
mostRecentlyUpdatedRoot !== null
? mostRecentlyUpdatedRoot.pendingLanes
: NoLanes;
}
return findTransitionLane(currentEventWipLanes, currentEventPendingLanes);
}
// 正常情况, 获取调度优先级
const schedulerPriority = getCurrentPriorityLevel();
let lane;
if (
(executionContext & DiscreteEventContext) !== NoContext &&
schedulerPriority === UserBlockingSchedulerPriority
) {
// executionContext 存在输入事件. 且调度优先级是用户阻塞性质
lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
} else {
// 调度优先级转换为车道模型
const schedulerLanePriority = schedulerPriorityToLanePriority(
schedulerPriority,
);
lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
}
return lane;
}可以看到requestUpdateLane的作用是返回一个合适的 update 优先级.
legacy 模式: 返回
SyncLaneblocking 模式: 返回
SyncLaneconcurrent 模式:
正常情况下, 根据当前的
调度优先级来生成一个lane.特殊情况下(处于 suspense 过程中), 会优先选择
TransitionLanes通道中的空闲通道(如果所有TransitionLanes通道都被占用, 就取最高优先级
最后通过scheduleUpdateOnFiber(current, lane, eventTime);函数, 把update.lane正式带入到了输入阶段.
export function scheduleUpdateOnFiber(
fiber: Fiber,
lane: Lane,
eventTime: number,
) {
if (lane === SyncLane) {
// legacy或blocking模式
if (
(executionContext & LegacyUnbatchedContext) !== NoContext &&
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
performSyncWorkOnRoot(root);
} else {
ensureRootIsScheduled(root, eventTime); // 注册回调任务
if (executionContext === NoContext) {
flushSyncCallbackQueue(); // 取消schedule调度 ,主动刷新回调队列,
}
}
} else {
// concurrent模式
ensureRootIsScheduled(root, eventTime);
}
}渲染优先级
fiber 优先级
Fiber 树初次构建
初次创建: 在
React应用首次启动时, 界面还没有渲染, 此时并不会进入对比过程, 相当于直接构造一棵全新的树.对比更新:
React应用启动后, 界面已经渲染. 如果再次发生更新, 创建新fiber之前需要和旧fiber进行对比. 最后构造的 fiber 树有可能是全新的, 也可能是部分更新的.
class App extends React.Component {
componentDidMount() {
console.log(`App Mount`);
console.log(`App 组件对应的fiber节点: `, this._reactInternals);
}
render() {
return (
<div className="app">
<header>header</header>
<Content />
</div>
);
}
}
class Content extends React.Component {
componentDidMount() {
console.log(`Content Mount`);
console.log(`Content 组件对应的fiber节点: `, this._reactInternals);
}
render() {
return (
<React.Fragment>
<p>1</p>
<p>2</p>
</React.Fragment>
);
}
}
export default App;在React中,组件的生命周期方法执行顺序是:父组件挂载完成之前,会先挂载子组件。但是注意,componentDidMount是在组件挂载之后(也就是渲染到DOM之后)调用的。因此,我们看到的顺序将是:先挂载子组件,再挂载父组件吗?实际上,React的挂载阶段是递归的,从父组件开始,然后到子组件,子组件挂载完成后,再回溯到父组件完成挂载。
具体来说,对于挂载阶段,React会执行以下操作:
创建组件实例(如果是类组件)
调用render方法获取子元素
递归地处理子元素(创建或更新对应的fiber节点)
在完成所有子组件的挂载后,再调用当前组件的componentDidMount。
启动阶段
在前文React 应用的启动过程中分析了 3 种启动模式的差异, 在进入react-reconciler包之前(调用updateContainer之前), 内存状态图如下:

console.log("document.getElementById('root')._reactRootContainer._internalRoot.current");然后进入react-reconciler包调用updateContainer 函数:
// ... 省略了部分代码
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): Lane {
// 获取当前时间戳
const current = container.current;
const eventTime = requestEventTime();
// 1. 创建一个优先级变量(车道模型)
const lane = requestUpdateLane(current);
// 2. 根据车道优先级, 创建update对象, 并加入fiber.updateQueue.pending队列
const update = createUpdate(eventTime, lane);
update.payload = { element };
callback = callback === undefined ? null : callback;
if (callback !== null) {
update.callback = callback;
}
enqueueUpdate(current, update);
// 3. 进入reconciler运作流程中的`输入`环节
scheduleUpdateOnFiber(current, lane, eventTime);
return lane;
}由于update对象的创建, 此时的内存结构如下:

最初的ReactElement对象<App/>被挂载到HostRootFiber.updateQueue.shared.pending.payload.element中, 后文fiber树构造过程中会再次变动
构造阶段
为了突出构造过程,排除干扰,先把内存状态图中的FiberRoot和HostRootFiber单独提出来(后文在此基础上添加):

// ...省略部分代码
export function scheduleUpdateOnFiber(
fiber: Fiber,
lane: Lane,
eventTime: number,
) {
// 标记优先级
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
if (lane === SyncLane) {
if (
(executionContext & LegacyUnbatchedContext) !== NoContext &&
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// 首次渲染, 直接进行`fiber构造`
performSyncWorkOnRoot(root);
}
// ...
}
}可以看到, 在Legacy模式下且首次渲染时, 有 2 个函数markUpdateLaneFromFiberToRoot和performSyncWorkOnRoot.
其中markUpdateLaneFromFiberToRoot(fiber, lane)函数在fiber树构造(对比更新)中才会发挥作用, 因为在初次创建时并没有与当前页面所对应的fiber树, 所以核心代码并没有执行, 最后直接返回了FiberRoot对象.
performSyncWorkOnRoot看起来源码很多, 初次创建中真正用到的就 2 个函数:
function performSyncWorkOnRoot(root) {
let lanes;
let exitStatus;
if (
root === workInProgressRoot &&
includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)
) {
// 初次构造时(因为root=fiberRoot, workInProgressRoot=null), 所以不会进入
} else {
// 1. 获取本次render的优先级, 初次构造返回 NoLanes
lanes = getNextLanes(root, NoLanes);
// 2. 从root节点开始, 至上而下更新
exitStatus = renderRootSync(root, lanes);
}
// 将最新的fiber树挂载到root.finishedWork节点上
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
// 进入commit阶段
commitRoot(root);
// ...后面的内容本节不讨论
}其中getNextLanes返回本次 render 的渲染优先级
function renderRootSync(root: FiberRoot, lanes: Lanes) {
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
// 如果fiberRoot变动, 或者update.lane变动, 都会刷新栈帧, 丢弃上一次渲染进度
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
// 刷新栈帧, legacy模式下都会进入
prepareFreshStack(root, lanes);
}
do {
try {
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
executionContext = prevExecutionContext;
// 重置全局变量, 表明render结束
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus;
}在renderRootSync中, 在执行fiber树构造前(workLoopSync)会先刷新栈帧prepareFreshStack(参考fiber 树构造(基础准备)).在这里创建了HostRootFiber.alternate, 重置全局变量workInProgress和workInProgressRoot等

循环构造
逻辑来到workLoopSync, 虽然本节在Legacy模式下进行讨论, 此处还是对比一下workLoopConcurrent
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}可以看到workLoopConcurrent相比于Sync, 会多一个停顿机制, 这个机制实现了时间切片和可中断渲染
// ... 省略部分无关代码
function performUnitOfWork(unitOfWork: Fiber): void {
// unitOfWork就是被传入的workInProgress
const current = unitOfWork.alternate;
let next;
next = beginWork(current, unitOfWork, subtreeRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// 如果没有派生出新的节点, 则进入completeWork阶段, 传入的是当前unitOfWork
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}可以明显的看出, 整个fiber树构造是一个深度优先遍历(可参考React 算法之深度优先遍历), 其中有 2 个重要的变量workInProgress和current(可参考前文fiber 树构造(基础准备)中介绍的双缓冲技术):
workInProgress和current都视为指针workInProgress指向当前正在构造的fiber节点current = workInProgress.alternate(即fiber.alternate), 指向当前页面正在使用的fiber节点. 初次构造时, 页面还未渲染, 此时current = null.
在深度优先遍历中, 每个fiber节点都会经历 2 个阶段:
探寻阶段
beginWork回溯阶段
completeWork
这 2 个阶段共同完成了每一个fiber节点的创建, 所有fiber节点则构成了fiber树.
探索阶段(beginWork)
beginWork(current, unitOfWork, subtreeRenderLanes)(源码地址)针对所有的 Fiber 类型, 其中的每一个 case 处理一种 Fiber 类型. updateXXX函数(如: updateHostRoot, updateClassComponent 等)的主要逻辑:
根据
ReactElement对象创建所有的fiber节点, 最终构造出fiber树形结构(设置return和sibling指针)设置
fiber.flags(二进制形式变量, 用来标记fiber节点 的增,删,改状态, 等待completeWork阶段处理)设置
fiber.stateNode局部状态(如Class类型节点:fiber.stateNode=new Class())
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const updateLanes = workInProgress.lanes;
if (current !== null) {
// update逻辑, 首次render不会进入
} else {
didReceiveUpdate = false;
}
// 1. 设置workInProgress优先级为NoLanes(最高优先级)
workInProgress.lanes = NoLanes;
// 2. 根据workInProgress节点的类型, 用不同的方法派生出子节点
switch (
workInProgress.tag // 只保留了本例使用到的case
) {
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
case Fragment:
return updateFragment(current, workInProgress, renderLanes);
}
}updateXXX函数(如: updateHostRoot, updateClassComponent 等)虽然 case 较多, 但是主要逻辑可以概括为 3 个步骤:
根据
fiber.pendingProps, fiber.updateQueue等输入数据状态, 计算fiber.memoizedState作为输出状态获取下级
ReactElement对象class 类型的
fiber节点构建
React.Component实例把新实例挂载到
fiber.stateNode上执行
render之前的生命周期函数执行
render方法, 获取下级reactElement根据实际情况, 设置
fiber.flags
function 类型的
fiber节点执行 function, 获取下级
reactElement根据实际情况, 设置
fiber.flags
HostComponent 类型(如:
div, span, button等)的fiber节点pendingProps.children作为下级reactElement如果下级节点是文本节点,则设置下级节点为 null. 准备进入
completeUnitOfWork阶段根据实际情况, 设置
fiber.flags
其他类型...
根据
ReactElement对象, 调用reconcileChildren生成Fiber子节点(只生成次级子节点)根据实际情况, 设置
fiber.flags
不同的updateXXX函数处理的fiber节点类型不同, 总的目的是为了向下生成子节点. 在这个过程中把一些需要持久化的数据挂载到fiber节点上(如fiber.stateNode,fiber.memoizedState等); 把fiber节点的特殊操作设置到fiber.flags(如:节点ref,class组件的生命周期,function组件的hook,节点删除等).
这里列出updateHostRoot, updateHostComponent的代码, 对于其他常用 case 的分析(如class类型, function类型), 在状态组件章节中进行探讨.
// 省略与本节无关代码
function updateHostRoot(current, workInProgress, renderLanes) {
// 1. 状态计算, 更新整合到 workInProgress.memoizedState中来
const updateQueue = workInProgress.updateQueue;
const nextProps = workInProgress.pendingProps;
const prevState = workInProgress.memoizedState;
const prevChildren = prevState !== null ? prevState.element : null;
cloneUpdateQueue(current, workInProgress);
// 遍历updateQueue.shared.pending, 提取有足够优先级的update对象, 计算出最终的状态 workInProgress.memoizedState
processUpdateQueue(workInProgress, nextProps, null, renderLanes);
const nextState = workInProgress.memoizedState;
// 2. 获取下级`ReactElement`对象
const nextChildren = nextState.element;
const root: FiberRoot = workInProgress.stateNode;
if (root.hydrate && enterHydrationState(workInProgress)) {
// ...服务端渲染相关, 此处省略
} else {
// 3. 根据`ReactElement`对象, 调用`reconcileChildren`生成`Fiber`子节点(只生成`次级子节点`)
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
return workInProgress.child;
}普通 DOM 标签类型的节点(如div,span,p),会进入updateHostComponent:
// ...省略部分无关代码
function updateHostComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
// 1. 状态计算, 由于HostComponent是无状态组件, 所以只需要收集 nextProps即可, 它没有 memoizedState
const type = workInProgress.type;
const nextProps = workInProgress.pendingProps;
const prevProps = current !== null ? current.memoizedProps : null;
// 2. 获取下级`ReactElement`对象
let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(type, nextProps);
if (isDirectTextChild) {
// 如果子节点只有一个文本节点, 不用再创建一个HostText类型的fiber
nextChildren = null;
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
// 特殊操作需要设置fiber.flags
workInProgress.flags |= ContentReset;
}
// 特殊操作需要设置fiber.flags
markRef(current, workInProgress);
// 3. 根据`ReactElement`对象, 调用`reconcileChildren`生成`Fiber`子节点(只生成`次级子节点`)
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}回溯阶段 completeWork
completeUnitOfWork(unitOfWork)(源码地址), 处理 beginWork 阶段已经创建出来的 fiber 节点, 核心逻辑:
调用
completeWork给
fiber节点(tag=HostComponent, HostText)创建 DOM 实例, 设置fiber.stateNode局部状态(如tag=HostComponent, HostText节点: fiber.stateNode 指向这个 DOM 实例).为 DOM 节点设置属性, 绑定事件(这里先说明有这个步骤, 详细的事件处理流程, 在
合成事件原理中详细说明).设置
fiber.flags标记
把当前
fiber对象的副作用队列(firstEffect和lastEffect)添加到父节点的副作用队列之后, 更新父节点的firstEffect和lastEffect指针.识别
beginWork阶段设置的fiber.flags, 判断当前fiber是否有副作用(增,删,改), 如果有, 需要将当前fiber加入到父节点的effects队列, 等待commit阶段处理.
function completeUnitOfWork(unitOfWork: Fiber): void {
let completedWork = unitOfWork;
// 外层循环控制并移动指针(`workInProgress`,`completedWork`等)
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
if ((completedWork.flags & Incomplete) === NoFlags) {
let next;
// 1. 处理Fiber节点, 会调用渲染器(调用react-dom包, 关联Fiber节点和dom对象, 绑定事件等)
next = completeWork(current, completedWork, subtreeRenderLanes); // 处理单个节点
if (next !== null) {
// 如果派生出其他的子节点, 则回到`beginWork`阶段进行处理
workInProgress = next;
return;
}
// 重置子节点的优先级
resetChildLanes(completedWork);
if (
returnFiber !== null &&
(returnFiber.flags & Incomplete) === NoFlags
) {
// 2. 收集当前Fiber节点以及其子树的副作用effects
// 2.1 把子节点的副作用队列添加到父节点上
if (returnFiber.firstEffect === null) {
returnFiber.firstEffect = completedWork.firstEffect;
}
if (completedWork.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
}
returnFiber.lastEffect = completedWork.lastEffect;
}
// 2.2 如果当前fiber节点有副作用, 将其添加到子节点的副作用队列之后.
const flags = completedWork.flags;
if (flags > PerformedWork) {
// PerformedWork是提供给 React DevTools读取的, 所以略过PerformedWork
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork;
} else {
returnFiber.firstEffect = completedWork;
}
returnFiber.lastEffect = completedWork;
}
}
} else {
// 异常处理, 本节不讨论
}
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
// 如果有兄弟节点, 返回之后再次进入`beginWork`阶段
workInProgress = siblingFiber;
return;
}
// 移动指针, 指向下一个节点
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
// 已回溯到根节点, 设置workInProgressRootExitStatus = RootCompleted
if (workInProgressRootExitStatus === RootIncomplete) {
workInProgressRootExitStatus = RootCompleted;
}
}function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case ClassComponent: {
// Class类型不做处理
return null;
}
case HostRoot: {
const fiberRoot = (workInProgress.stateNode: FiberRoot);
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}
if (current === null || current.child === null) {
// 设置fiber.flags标记
workInProgress.flags |= Snapshot;
}
return null;
}
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
// update逻辑, 初次render不会进入
} else {
const currentHostContext = getHostContext();
// 1. 创建DOM对象
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// 2. 把子树中的DOM对象append到本节点的DOM对象之后
appendAllChildren(instance, workInProgress, false, false);
// 设置stateNode属性, 指向DOM对象
workInProgress.stateNode = instance;
if (
// 3. 设置DOM对象的属性, 绑定事件等
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
// 设置fiber.flags标记(Update)
markUpdate(workInProgress);
}
if (workInProgress.ref !== null) {
// 设置fiber.flags标记(Ref)
markRef(workInProgress);
}
return null;
}
}
}详细过程总结
构造前
在上文已经说明, 进入循环构造前会调用prepareFreshStack刷新栈帧, 在进入fiber树构造循环之前, 保持这这个初始化状态:

performUnitOfWork第 1 次调用(只执行beginWork):
执行前:
workInProgress指针指向HostRootFiber.alternate对象, 此时current = workInProgress.alternate指向fiberRoot.current是非空的(初次构造, 只在根节点时,current非空).执行过程: 调用
updateHostRoot在
reconcileChildren阶段, 向下构造次级子节点fiber(<App/>), 同时设置子节点(fiber(<App/>))fiber.flags |= Placement
执行后: 返回下级节点
fiber(<App/>), 移动workInProgress指针指向子节点fiber(<App/>)

performUnitOfWork第 2 次调用(只执行beginWork):
执行前:
workInProgress指针指向fiber(<App/>)节点, 此时current = null执行过程: 调用
updateClassComponent本示例中, class 实例存在生命周期函数
componentDidMount, 所以会设置fiber(<App/>)节点workInProgress.flags |= Update另外也会为了
React DevTools能够识别状态组件的执行进度, 会设置workInProgress.flags |= PerformedWork(在commit阶段会排除这个flag, 此处只是列出workInProgress.flags的设置场景, 不讨论React DevTools)需要注意
classInstance.render()在本步骤执行后, 虽然返回了render方法中所有的ReactElement对象, 但是随后reconcileChildren只构造次级子节点在
reconcileChildren阶段, 向下构造次级子节点div
执行后: 返回下级节点
fiber(div), 移动workInProgress指针指向子节点fiber(div)

performUnitOfWork第 3 次调用(只执行beginWork):
执行前:
workInProgress指针指向fiber(div)节点, 此时current = null执行过程: 调用
updateHostComponent在
reconcileChildren阶段, 向下构造次级子节点(本示例中,div有 2 个次级子节点)
执行后: 返回下级节点
fiber(header), 移动workInProgress指针指向子节点fiber(header)

performUnitOfWork第 4 次调用(执行beginWork和completeUnitOfWork):
beginWork执行前:workInProgress指针指向fiber(header)节点, 此时current = nullbeginWork执行过程: 调用updateHostComponent本示例中
header的子节点是一个直接文本节点,设置nextChildren = null(直接文本节点并不会被当成具体的fiber节点进行处理, 而是在宿主环境(父组件)中通过属性进行设置. 所以无需创建HostText类型的fiber节点, 同时节省了向下遍历开销.).由于
nextChildren = null, 经过reconcileChildren阶段处理后, 返回值也是null
beginWork执行后: 由于下级节点为null, 所以进入completeUnitOfWork(unitOfWork)函数, 传入的参数unitOfWork实际上就是workInProgress(此时指向fiber(header)节点)

completeUnitOfWork执行前: workInProgress指针指向fiber(header)节点
completeUnitOfWork执行过程: 以fiber(header)为起点, 向上回溯
第 1 次循环:
执行
completeWork函数创建
fiber(header)节点对应的DOM实例, 并append子节点的DOM实例设置
DOM属性, 绑定事件等(本示例中, 节点fiber(header)没有事件绑定)
上移副作用队列: 由于本节点
fiber(header)没有副作用(fiber.flags = 0), 所以执行之后副作用队列没有实质变化(目前为空).向上回溯: 由于还有兄弟节点, 把
workInProgress指针指向下一个兄弟节点fiber(<Content/>), 退出completeUnitOfWork.
performUnitOfWork第 5 次调用(执行beginWork):
执行前:
workInProgress指针指向fiber(<Content/>)节点.执行过程: 这是一个
class类型的节点, 与第 2 次调用逻辑一致.执行后: 返回下级节点
fiber(p), 移动workInProgress指针指向子节点fiber(p)
第 1 次循环:
执行
completeWork函数: 创建fiber(p)节点对应的DOM实例, 并append子树节点的DOM实例上移副作用队列: 由于本节点
fiber(p)没有副作用, 所以执行之后副作用队列没有实质变化(目前为空).向上回溯: 由于没有兄弟节点, 把
workInProgress指针指向父节点fiber(<Content/>)
第 2 次循环:
执行
completeWork函数: class 类型的节点不做处理上移副作用队列:
本节点
fiber(<Content/>)的flags标志位有改动(completedWork.flags > PerformedWork), 将本节点添加到父节点(fiber(div))的副作用队列之后(firstEffect和lastEffect属性分别指向副作用队列的首部和尾部).
向上回溯: 把
workInProgress指针指向父节点fiber(div)
第 3 次循环:
执行
completeWork函数: 创建fiber(div)节点对应的DOM实例, 并append子树节点的DOM实例上移副作用队列:
本节点
fiber(div)的副作用队列不为空, 将其拼接到父节点fiber<App/>的副作用队列后面.
向上回溯: 把
workInProgress指针指向父节点fiber(<App/>)
第 4 次循环:
执行
completeWork函数: class 类型的节点不做处理上移副作用队列:
本节点
fiber(<App/>)的副作用队列不为空, 将其拼接到父节点fiber(HostRootFiber)的副作用队列上.本节点
fiber(<App/>)的flags标志位有改动(completedWork.flags > PerformedWork), 将本节点添加到父节点fiber(HostRootFiber)的副作用队列之后.最后队列的顺序是
子节点在前, 本节点在后
向上回溯: 把
workInProgress指针指向父节点fiber(HostRootFiber)
第 5 次循环:
执行
completeWork函数: 对于HostRoot类型的节点, 初次构造时设置workInProgress.flags |= Snapshot向上回溯: 由于父节点为空, 无需进入处理副作用队列的逻辑. 最后设置
workInProgress=null, 并退出completeUnitOfWork
Fiber 树对比更新
三种更新方式
如要主动发起更新, 有 3 种常见方式:
Class组件中调用setState.Function组件中调用hook对象暴露出的dispatchAction.在
container节点上重复调用render
setState
在Component对象的原型上挂载有setState
Component.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};在fiber 树构造(初次创建)中的beginWork阶段, class 类型的组件初始化完成之后, this.updater对象如下
const classComponentUpdater = {
isMounted,
enqueueSetState(inst, payload, callback) {
// 1. 获取class实例对应的fiber节点
const fiber = getInstance(inst);
// 2. 创建update对象
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber); // 确定当前update对象的优先级
const update = createUpdate(eventTime, lane);
update.payload = payload;
if (callback !== undefined && callback !== null) {
update.callback = callback;
}
// 3. 将update对象添加到当前Fiber节点的updateQueue队列当中
enqueueUpdate(fiber, update);
// 4. 进入reconciler运作流程中的`输入`环节
scheduleUpdateOnFiber(fiber, lane, eventTime); // 传入的lane是update优先级
},
};dispatchAction
在function类型组件中, 如果使用hook(useState), 则可以通过hook api暴露出的dispatchAction
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
// 1. 创建update对象
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber); // 确定当前update对象的优先级
const update: Update<S, A> = {
lane,
action,
eagerReducer: null,
eagerState: null,
next: (null: any),
};
// 2. 将update对象添加到当前Hook对象的updateQueue队列当中
const pending = queue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
// 3. 请求调度, 进入reconciler运作流程中的`输入`环节.
scheduleUpdateOnFiber(fiber, lane, eventTime); // 传入的lane是update优先级
}重复调度 render函数
import ReactDOM from 'react-dom';
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(element, document.getElementById('root'));
}
setInterval(tick, 1000);