在创建react项目的时候,入口文件总是有这样一行代码
root.render(<App />)
所以 root.render() 执行是怎样的? 下面就来看看。
之前的文章就提及,root是一个 ReactDOMRoot 对象,其原型链上有 render 和 unmount 方法。
ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render =
// $FlowFixMe[missing-this-annot]
function (children: ReactNodeList): void {
// this就是ReactDOMRoot实例,
// this._internalRoot就是fiberRoot对象
const root = this._internalRoot;
if (root === null) {
throw new Error('Cannot update an unmounted root.');
}
//执行更新
updateContainer(children, root, null, null);
};
root.render流程图
updateContainer
updateContainer 函数是 React 中用于触发更新容器内容的核心函数,它的主要作用是启动一个更新流程,将 新的 React 元素(element)渲染到 指定的容器(container)中。该函数会获取当前的 根 Fiber 节点,请求一个更新车道(lane)来确定更新的优先级,然后调用 updateContainerImpl 函数执行具体的更新操作,最后返回更新车道。
函数参数含义
- element:类型为 ReactNodeList,表示要渲染到容器中的 React 元素或元素列表。它可以是单个 React 元素,也可以是多个元素组成的数组。
- container:类型为 OpaqueRoot,是一个不透明的根对象,实际上代表了 FiberRootNode,包含了整个 React 应用的根节点信息。
- parentComponent:类型为 ?React$Component<any, any>,是一个可选的父组件。在某些情况下,可能需要指定父组件来进行更新操作,但通常为 null。
- callback:类型为 ?Function,是一个可选的回调函数,在更新完成后会被调用。
function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,// null
callback: ?Function,// null
): Lane {
//container 是 FiberRootNode 对象,container.current 指向当前的根 Fiber 节点。这个根 Fiber 节点代表了当前的渲染状态,是整个 React 应用的起点。
const current = container.current;
//同步直接返回 `SyncLane` = 1。以后开启并发和异步等返回的值就不一样了,目前只有同步这个模式
//请求 根Fiber 更新车道(lane),用于确定更新的优先级。
const lane = requestUpdateLane(current);
// 负责执行具体的更新逻辑。它会根据传入的根 Fiber 节点、更新车道、新的 React 元素、容器、父组件和回调函数,进行协调(reconciliation)操作,比较新旧元素的差异,并决定如何更新 DOM 以反映新的元素。
updateContainerImpl(
current,// 根fiber
lane,// 更新车道
element,// 子节点
container,// fiberRoot
parentComponent,// null
callback,
);
return lane;
}
updateContainerImpl
updateContainerImpl 函数是 React 中处理容器更新的具体实现函数,其主要任务是在接收到更新请求后,对更新进行一系列的准备工作,包括标记性能信息、获取上下文、创建更新对象、将更新对象入队,最后调度更新任务以开始执行更新操作。
函数参数含义:
- rootFiber:类型为 Fiber,代表 React 应用的根 Fiber 节点,是整个更新操作的起始点。
- lane:类型为 Lane,表示本次更新的优先级车道,用于决定更新任务的执行顺序和优先级。
- element:类型为 ReactNodeList,是需要更新到容器中的 React 元素或元素列表。
- container:类型为 OpaqueRoot,是一个不透明的根对象,通常代表 FiberRootNode,包含了整个 React 应用的根节点信息。
- parentComponent:类型为 ?React$Component<any, any>,是可选的父组件,用于获取上下文。
- callback:类型为 ?Function,是可选的回调函数,在更新完成后会被调用。
function updateContainerImpl(
rootFiber: Fiber,// 根节点fiber
lane: Lane,// 更新优先级车道
element: ReactNodeList,// 需要更新的react元素
container: OpaqueRoot,//FiberRootNode
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): void {
//获取当前节点和子节点的上下文
const context = getContextForSubtree(parentComponent);
if (container.context === null) {
// 将获取到的上下文赋值给 container.context
container.context = context;
} else {
// 将获取到的上下文赋值给 container.pendingContext
container.pendingContext = context;
}
//创建一个 update 更新对象
const update = createUpdate(lane);
// 记录update的载荷信息
update.payload = {element};
// 如果有回调信息,保存
callback = callback === undefined ? null : callback;
if (callback !== null) {
// 存储callback
update.callback = callback;
}
// 将创建好的更新对象 update 加入到根 Fiber 节点的更新队列中。该函数返回根 FiberRootNode 对象。
// root 为FiberRoot
const root = enqueueUpdate(rootFiber, update, lane);
// 调度更新任务
if (root !== null) {
// 启动一个与当前更新车道相关的计时器,用于记录更新操作的时间。
// startUpdateTimerByLane(lane);
// 根据更新的优先级车道安排更新任务的执行顺序。
scheduleUpdateOnFiber(root, rootFiber, lane);
// 处理过渡(transitions)相关的逻辑,确保过渡效果的正确执行。
// entangleTransitions(root, rootFiber, lane);
}
}
工具函数 createUpdate
createUpdate 函数的主要作用是创建一个更新对象(Update),该对象用于描述 React 组件状态或属性的更新操作。在 React 的更新机制中,更新操作会被封装成一个个 Update 对象,然后被添加到更新队列中,后续会根据这些更新对象来计算新的状态并更新组件。
function createUpdate(lane: Lane): Update<mixed> {
const update: Update<mixed> = {
lane,// 优先级车道
tag: UpdateState,// 更新类型
payload: null,// payload 用于存储更新操作的具体数据,例如新的状态值、属性值等。在后续的更新过程中,会根据 payload 的内容来计算新的状态。
callback: null,// 回调,更新完成后执行
next: null,// next 是一个指向链表中下一个更新对象的指针。在 React 中,更新对象会以链表的形式存储在更新队列中,通过 next 指针可以将多个更新对象连接起来。
};
return update;
}
// 表示普通状态的更新,例如hook更新
export const UpdateState = 0;
// 表示替换状态的操作。当使用 ReplaceState 类型的更新时,会直接用新的状态对象替换当前的状态对象,而不是像 UpdateState 那样合并状态。
export const ReplaceState = 1;
// 表示强制更新操作。当调用 forceUpdate 方法(类组件)时,会触发 ForceUpdate 类型的更新。强制更新会绕过状态和属性的浅比较,直接触发组件的重新渲染。
export const ForceUpdate = 2;
// CaptureUpdate 通常与错误边界和捕获阶段的更新有关。在 React 的错误处理机制中,捕获阶段可以捕获子组件抛出的错误,并进行相应的处理。CaptureUpdate 可能用于在捕获阶段触发的状态更新操作。
export const CaptureUpdate = 3;
工具函数之 enqueueUpdate
enqueueUpdate
函数的主要作用是将一个更新对象 update
加入到指定 Fiber
节点的更新队列中。更新队列用于存储组件的状态更新操作,在后续的渲染过程中,React 会根据这些更新操作来计算新的状态。该函数会根据 Fiber
节点的状态和更新阶段,选择不同的方式将更新对象加入队列,并标记从该 Fiber
节点到根节点的更新车道,最后返回根 Fiber
节点。
函数参数含义
fiber
:类型为Fiber
,代表要加入更新的Fiber
节点,Fiber
是 React Fiber 架构中的核心数据结构,每个Fiber
节点对应一个组件实例。update
:类型为Update<State>
,是一个更新对象,包含了更新的具体信息,如更新的类型、载荷等。lane
:类型为Lane
,表示更新的优先级车道,用于确定更新的执行顺序和优先级。
function enqueueUpdate<State>(
fiber: Fiber,
update: Update<State>,
lane: Lane,
): FiberRoot | null {
// 根fiber的更新队列
const updateQueue = fiber.updateQueue;
// 如果 updateQueue 为空,说明该 fiber 已经被卸载,直接返回 null。
if (updateQueue === null) {
// fiber 被卸载时
return null;
}
// 从 updateQueue 中获取共享队列 sharedQueue。sharedQueue 是一个对象,包含三个属性:interleaved、lanes 和 pending。返回一个对象 {interleaved:null, lanes:0, pending:null}
const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
// 如果 fiber 处于不安全的类渲染阶段
if (isUnsafeClassRenderPhaseUpdate(fiber)) {
// pending 永远指向最后一个更新
const pending = sharedQueue.pending;
// 如果 pending 为空,说明这是第一个更新,需要创建一个循环单链表,将 update.next 指向 update 自己。
if (pending === null) {
update.next = update;
} else {
// 如果 pending 不为空,取出第一个更新并插入新的更新,使其成为循环单链表的一部分。
update.next = pending.next;
pending.next = update;
}
// 更新 sharedQueue.pending 指向新的 update。
sharedQueue.pending = update;
// 调用 unsafe_markUpdateLaneFromFiberToRoot 标记更新从 fiber 到根 Fiber,并返回根 Fiber。
return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane);
} else {
// 调用 enqueueConcurrentClassUpdate 函数处理并发类更新。该函数会根据并发更新的规则将更新对象加入队列,并进行相应的处理
return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
}
}
在初始的状态,sharedQueue.pending
为 null。第一次更新的时候 sharedQueue.pending
指向 update,并且 update.next 指向 update 自己,形成一个循环链表。
第二次更新的时候 sharedQueue.pending
更新为指向 update(新传入的),update.next 指向 update1(原来的pending.update),update1.next 指向 update,形成新的循环链表。
type Update<State> = {
lane: Lane,
tag: 0 | 1 | 2 | 3,
payload: any,
callback: (() => mixed) | null,
next: Update<State> | null,
};
工具函数之 enqueueConcurrentClassUpdate
将一个并发类更新对象 update
加入到指定 Fiber
节点的并发更新队列中,并返回该 Fiber
节点对应的根 Fiber
节点。此函数用于处理 React 并发模式下的类组件更新,确保更新操作能被正确地加入队列并关联到根节点
function enqueueConcurrentClassUpdate<State>(
fiber: Fiber,
queue: ClassQueue<State>,// 共享队列
update: ClassUpdate<State>,// 更新对象
lane: Lane,
): FiberRoot | null {
// 并发更新队列
const concurrentQueue: ConcurrentQueue = (queue: any);
// 并发更新对象
const concurrentUpdate: ConcurrentUpdate = (update: any);
// 与前面的enqueueUpdate不同
// 将转换后的并发更新队列 concurrentQueue 和并发更新对象 concurrentUpdate 加入到指定 Fiber 节点的更新队列中,并指定更新的优先级车道 lane。
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
// 获取 FiberRoot 节点
return getRootForUpdatedFiber(fiber);
}
const concurrentQueues: Array<any> = [];
// concurrentQueuesIndex 是一个全局索引,用于记录当前存储位置。
let concurrentQueuesIndex = 0;
// concurrentlyUpdatedLanes 是一个全局变量,用于记录所有并发更新的车道集合。
let concurrentlyUpdatedLanes: Lanes = NoLanes;
function enqueueUpdate(
fiber: Fiber,
queue: ConcurrentQueue | null,// 更新队列
update: ConcurrentUpdate | null,// 更新对象
lane: Lane,
) {
// concurrentQueues 是一个全局数组,用于临时存储所有并发更新的相关信息。
concurrentQueues[concurrentQueuesIndex++] = fiber;
concurrentQueues[concurrentQueuesIndex++] = queue;
concurrentQueues[concurrentQueuesIndex++] = update;
concurrentQueues[concurrentQueuesIndex++] = lane;
concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);
// 更新fiber节点的lanes
fiber.lanes = mergeLanes(fiber.lanes, lane); // fiber.lanes | lanes
const alternate = fiber.alternate;
if (alternate !== null) {
// 更新alternate的lanes
alternate.lanes = mergeLanes(alternate.lanes, lane);
}
}
工具函数之 getRootForUpdatedFiber
从一个被更新的 Fiber
节点出发,向上遍历找到对应的根 Fiber
节点(FiberRoot
)。
function getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null {
// sourceFiber表示需要查找根节点的起始 Fiber 节点。
// 检测是否在已卸载的 Fiber 节点上进行更新。
// detectUpdateOnUnmountedFiber(sourceFiber, sourceFiber);
// 根fiber
let node = sourceFiber;
// 父节点
let parent = node.return;
// 向上遍历 Fiber 树
while (parent !== null) {
// 检测是否在已卸载的 Fiber 节点上进行更新。
// detectUpdateOnUnmountedFiber(sourceFiber, node);
node = parent;
parent = node.return;
}
return node.tag === HostRoot ? (node.stateNode: FiberRoot) : null;
}
scheduleUpdateOnFiber
其主要功能是为指定的 Fiber
节点安排一个更新任务。该函数会根据当前的渲染状态、更新的优先级以及其他相关条件,对更新任务进行不同的处理,确保更新能够正确、高效地被调度和执行。
root
:类型为FiberRoot
,代表 React 应用的根节点,是整个Fiber
树的根。fiber
:类型为Fiber
,表示需要进行更新的具体Fiber
节点。lane
:类型为Lane
,代表此次更新的优先级车道。
function scheduleUpdateOnFiber(root: FiberRoot,fiber: Fiber,lane: Lane,) {
// 检查当前 root 是否处于挂起状态。
// 若根节点 root 是正在进行渲染的根节点 workInProgressRoot,并且渲染因数据未就绪而挂起(workInProgressSuspendedReason === SuspendedOnData),或者根节点有未完成的提交需要取消(root.cancelPendingCommit !== null)
if (
(root === workInProgressRoot &&
workInProgressSuspendedReason === SuspendedOnData) ||
root.cancelPendingCommit !== null
) {
// 调用 prepareFreshStack 函数为 root 准备一个新的渲染栈
prepareFreshStack(root, NoLanes);
// 用于标记是否尝试对整个树进行渲染操作,这里设置为 false 表示没有尝试对整个树进行渲染。
const didAttemptEntireTree = false;
// 调用 markRootSuspended 函数标记根节点为挂起状态,同时传入相关的渲染车道和是否尝试对整个树进行渲染的标记
markRootSuspended(
root,// 要标记的根 Fiber 节点。
workInProgressRootRenderLanes,// 正在进行渲染的根节点的渲染车道,用于表示渲染的优先级。
workInProgressDeferredLane,// 正在进行的延迟车道,可能与延迟渲染或更新相关。
didAttemptEntireTree,// 标记是否尝试对整个树进行渲染。
);
}
// 调用 markRootUpdated 函数,标记根节点有一个挂起的更新,并且记录更新的优先级车道 lane。
markRootUpdated(root, lane);
// 检查当前是否处于渲染阶段,并且更新的 root 是正在进行渲染的 root。
if ((executionContext & RenderContext) !== NoLanes && root === workInProgressRoot) {
// Track lanes that were updated during the render phase
// 调用 mergeLanes 函数将当前更新的车道 lane 合并到 workInProgressRootRenderPhaseUpdatedLanes 中,记录渲染阶段的更新车道
workInProgressRootRenderPhaseUpdatedLanes = mergeLanes(
workInProgressRootRenderPhaseUpdatedLanes,
lane,
);
} else {
// 处理正常更新(非渲染阶段)
if (root === workInProgressRoot) {
// 如果更新的 root 是正在进行渲染的 root,且当前不在渲染阶段,将更新的车道 lane 合并到 workInProgressRootInterleavedUpdatedLanes 中。
if ((executionContext & RenderContext) === NoContext) {
workInProgressRootInterleavedUpdatedLanes = mergeLanes(
workInProgressRootInterleavedUpdatedLanes,
lane,
);
}
// 如果 root 处于延迟挂起状态,再次标记 root 为挂起状态。
if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
const didAttemptEntireTree = false;
markRootSuspended(
root,
workInProgressRootRenderLanes,
workInProgressDeferredLane,
didAttemptEntireTree,
);
}
}
// 调用 ensureRootIsScheduled 函数确保根节点被正确调度。
ensureRootIsScheduled(root);
}
}
ensureRootIsScheduled
ensureRootIsScheduled
函数的主要作用是确保 FiberRoot
节点被正确调度。它会将 FiberRoot
节点添加到调度列表中,标记可能存在待处理的同步工作,安排微任务来处理根节点的调度,并且根据配置决定是否在微任务期间为根节点安排任务。
let firstScheduledRoot: FiberRoot | null = null;
let lastScheduledRoot: FiberRoot | null = null;
let didScheduleMicrotask: boolean = false;
function ensureRootIsScheduled(root: FiberRoot): void {
// 首先检查 root 是否已经在调度列表中。
// 如果 root 等于 lastScheduledRoot 或者 root.next 不为 null,说明该根节点已经被调度,直接跳过后续添加操作。
if (root === lastScheduledRoot || root.next !== null) {
// Fast path. This root is already scheduled.
} else {
// 若 lastScheduledRoot 为 null,表示调度列表为空,将 firstScheduledRoot 和 lastScheduledRoot 都设置为 root,即该根节点成为调度列表中的第一个也是最后一个节点。
if (lastScheduledRoot === null) {
firstScheduledRoot = lastScheduledRoot = root;
} else {
// 若 lastScheduledRoot 不为 null,将 lastScheduledRoot.next 设置为 root,并将 lastScheduledRoot 更新为 root,即将该根节点添加到调度列表的末尾。
lastScheduledRoot.next = root;
lastScheduledRoot = root;
}
}
// 将 mightHavePendingSyncWork 标记为 true,表示可能存在待处理的同步工作。这可能会影响后续的调度决策,例如在某些情况下需要优先处理同步工作。
mightHavePendingSyncWork = true;
// 检查 didScheduleMicrotask 标志,如果为 false,说明还没有安排微任务来处理根节点的调度。
if (!didScheduleMicrotask) {
// 将 didScheduleMicrotask 设置为 true,表示已经安排了微任务。
didScheduleMicrotask = true;
// 用 scheduleImmediateTask 函数,安排一个立即执行的微任务,执行 processRootScheduleInMicrotask 函数,该函数可能会处理根节点的调度逻辑。
scheduleImmediateTask(processRootScheduleInMicrotask);
}
// 检查 enableDeferRootSchedulingToMicrotask 配置项,如果为 false,表示不延迟根节点的调度到微任务中。
if (!enableDeferRootSchedulingToMicrotask) {
// 调用 scheduleTaskForRootDuringMicrotask 函数,在微任务期间为根节点安排任务,now() 函数返回当前时间,可能用于确定任务的调度时间。
scheduleTaskForRootDuringMicrotask(root, now());
}
}
scheduleImmediateTask(processRootScheduleInMicrotask) 立即执行的微任务。即当当前宏任务执行完毕后立即执行。
工具函数之 scheduleImmediateTask
scheduleImmediateTask
函数的作用是安排一个立即执行的任务。它会根据当前环境是否支持微任务(microtasks)来选择合适的执行方式
function scheduleImmediateTask(cb: () => mixed) {
// 参数cb为立即执行的任务
// 支持微任务
if (supportsMicrotasks) {
// 把传入的回调函数安排到微任务队列中。当宏任务处理完后在再次执行这里
scheduleMicrotask(() => {
// 获取当前的执行上下文。
const executionContext = getExecutionContext();
// 检查当前执行上下文是否处于渲染(RenderContext)或提交(CommitContext)阶段。
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
// 处于渲染阶段或提交阶段, 安排回调函数的执行
Scheduler_scheduleCallback(ImmediateSchedulerPriority, cb);
return;
}
// 执行回调函数
cb();
});
} else {
// If microtasks are not supported, use Scheduler.
// 安排回调函数的执行
Scheduler_scheduleCallback(ImmediateSchedulerPriority, cb);
}
}
const scheduleMicrotask: any =
typeof queueMicrotask === 'function'
? queueMicrotask
: typeof localPromise !== 'undefined'
? callback =>
localPromise.resolve(null).then(callback).catch(handleErrorInNextTick)
: scheduleTimeout; // TODO: Determine the best fallback here.
const localPromise = typeof Promise === 'function' ? Promise : undefined;
Window:queueMicrotask() 方法 - Web API | MDN
工具函数之 scheduleCallback
unstable_scheduleCallback
函数是 React 调度机制中的核心函数,用于根据任务的优先级和延迟时间,将任务分配到不同的队列(taskQueue
或 timerQueue
)中,并调度相应的宿主回调(如浏览器事件循环)来执行任务。其核心逻辑包括:
- 根据优先级和延迟时间计算任务的开始时间和过期时间。
- 将延迟任务加入
timerQueue
,非延迟任务加入taskQueue
。 - 调度宿主超时(
requestHostTimeout
)或回调(requestHostCallback
)以触发任务执行。
const scheduleCallback = Scheduler.unstable_scheduleCallback;
// 待执行任务队列
var taskQueue: Array<Task> = [];
// 定时器任务队列
var timerQueue: Array<Task> = [];
function unstable_scheduleCallback(
priorityLevel: PriorityLevel,// 任务优先级
callback: Callback,// 回调函数
options?: {delay: number},// delay 延迟任务的毫秒
): Task {
//
var currentTime = getCurrentTime();
// 定义任务开始时间
var startTime;
if (typeof options === 'object' && options !== null) {
var delay = options.delay;
if (typeof delay === 'number' && delay > 0) {
// 任务开始时间, 当前时间加上延迟时间
startTime = currentTime + delay;
} else {
startTime = currentTime;
}
} else {
startTime = currentTime;
}
// 根据优先级设置超时时间
var timeout;
// 车道优先级
switch (priorityLevel) {
case ImmediatePriority:// 立即超市
// Times out immediately
timeout = -1;
break;
case UserBlockingPriority:
// Eventually times out
timeout = userBlockingPriorityTimeout;
break;
case IdlePriority:// 永不超时
// Never times out
timeout = maxSigned31BitInt;
break;
case LowPriority:
// Eventually times out
timeout = lowPriorityTimeout;
break;
case NormalPriority:
default:
// Eventually times out
timeout = normalPriorityTimeout;
break;
}
// 过期时间
var expirationTime = startTime + timeout;
// 创建新任务对象
var newTask: Task = {
id: taskIdCounter++, // 任务ID
callback,// 回调函数
priorityLevel,// 任务优先级
startTime,// 开始时间
expirationTime,// 过期时间
sortIndex: -1,// 排序索引
};
// 根据开始时间加入不同队列
// 延迟任务(startTime > currentTime)
if (startTime > currentTime) {
// This is a delayed task.
// 将任务的排序索引设置为开始时间。
newTask.sortIndex = startTime;
// 将任务加入 timerQueue(延迟任务队列)。
push(timerQueue, newTask);
// 如果 taskQueue 为空 且 当前任务是 timerQueue 中最早的延迟任务
if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
// All tasks are delayed, and this is the task with the earliest delay.
// 标记是否已经安排了一个主机超时任务。
if (isHostTimeoutScheduled) {
// Cancel an existing timeout.
cancelHostTimeout();
} else {
isHostTimeoutScheduled = true;
}
// Schedule a timeout.
// requestHostTimeout设置一个定时器任务
// handleTimeout 是超时后需要执行的回调函数,通常用于处理延迟任务队列中的任务。
// startTime - currentTime 表示从当前时间到新任务开始执行的时间间隔,即需要等待的时间。
requestHostTimeout(handleTimeout, startTime - currentTime);
}
} else {
// 非延迟任务(startTime <= currentTime)
//将任务的排序索引设置为过期时间。
newTask.sortIndex = expirationTime;
// 将任务加入 taskQueue(待执行任务队列)。
push(taskQueue, newTask);
// 如果没有正在调度的宿主回调,并且当前没有正在执行的工作,则安排一个宿主回调。
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback();
}
}
return newTask;
}
requestHostCallback
函数是 React 调度系统中与宿主环境(通常指浏览器)交互的关键函数之一,其主要作用是请求宿主环境调度执行任务。该函数会检查消息循环是否正在运行,如果没有运行,则启动消息循环,并安排执行任务直到达到截止时间。
function requestHostCallback() {
if (!isMessageLoopRunning) {
isMessageLoopRunning = true;
schedulePerformWorkUntilDeadline();
}
}
flushWork
flushWork
函数是 React 任务调度系统中的核心执行函数,主要用于触发任务的实际执行。它会在任务队列准备好后,调用 workLoop
函数循环处理任务,直到没有可执行的任务或需要向宿主环境让步。同时,该函数还负责管理任务执行的状态标记(如是否正在执行、是否需要调度宿主回调等),确保任务执行流程的正确性和完整性。
function flushWork(initialTime: number) {
// 将 “是否已安排宿主回调” 的标记置为 false。这意味着本次任务执行完毕后,下次再有任务时需要重新调度宿主回调(如通过 requestHostCallback)。
isHostCallbackScheduled = false;
// 如果存在已安排的超时任务(如延迟执行的定时器任务),但当前需要执行的是立即任务(非延迟任务),则取消超时任务。
if (isHostTimeoutScheduled) {
// We scheduled a timeout but it's no longer needed. Cancel it.
isHostTimeoutScheduled = false;
cancelHostTimeout();
}
// 用于防止任务执行过程中被重复调度,确保同一时间只有一个任务循环在运行。
isPerformingWork = true;
// previousPriorityLevel 保存当前的优先级级别
const previousPriorityLevel = currentPriorityLevel;
try {
return workLoop(initialTime);
} finally {
// 清空当前正在处理的任务,避免内存泄漏。
currentTask = null;
// 恢复之前保存的优先级级别。
currentPriorityLevel = previousPriorityLevel;
// 标记任务执行结束,允许下次调度。
isPerformingWork = false;
}
}
workLoop
workLoop
函数是一个任务调度的核心循环,它负责在给定的初始时间内,持续从任务队列(taskQueue
)中取出任务并执行,直到满足某些条件才停止。同时,它还会处理定时器队列(timerQueue
)中的任务,确保任务能在合适的时间执行。
function workLoop(initialTime: number) {
let currentTime = initialTime;
advanceTimers(currentTime);
// 使用 peek 函数从任务队列中取出第一个任务赋值给 currentTask,没有则为null
currentTask = peek(taskQueue);
// 进入 while 循环,只要任务队列中有任务,并且调度器没有因为调试模式而暂停,就会继续循环。
while (
currentTask !== null &&
!(enableSchedulerDebugging && isSchedulerPaused)
) {
// 如果当前任务的过期时间大于当前时间,并且需要向宿主环境让步(例如浏览器需要处理其他事件),则跳出循环。
if (currentTask.expirationTime > currentTime && shouldYieldToHost()) {
break;
}
// 取出当前任务的回调函数 callback。
const callback = currentTask.callback;
if (typeof callback === 'function') {
// 将当前任务的回调函数置为 null,避免重复执行。
currentTask.callback = null;
// 设置当前的优先级为当前任务的优先级。
currentPriorityLevel = currentTask.priorityLevel;
// 判断当前任务是否已经过期。
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
// 执行回调函数 callback,并传入任务是否过期的标志,得到返回的延续回调函数 continuationCallback。
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
// 处理延续回调函数
if (typeof continuationCallback === 'function') {
// 如果延续回调函数是一个函数,则将其赋值给当前任务的回调函数,再次推进定时器,然后返回 true 表示还有额外的工作。
currentTask.callback = continuationCallback;
advanceTimers(currentTime);
return true;
} else {
// 如果延续回调函数不是一个函数,并且当前任务是任务队列的第一个任务,则从任务队列中移除该任务,再次推进定时器。
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
advanceTimers(currentTime);
}
} else {
// 移除该任务
pop(taskQueue);
}
// 取出下一个任务
currentTask = peek(taskQueue);
}
// 判断是否还有任务
if (currentTask !== null) {
return true;
} else {
// 如果任务队列中没有任务,检查定时器队列中是否有任务。如果有,使用 requestHostTimeout 函数在合适的时间调用 handleTimeout 函数来处理定时器任务,然后返回 false 表示没有额外的工作。
const firstTimer = peek(timerQueue);
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
return false;
}
}
// Tasks are stored on a min heap
var taskQueue: Array<Task> = [];
var timerQueue: Array<Task> = [];
//opaque type 是 Flow 特有的不透明类型声明。不透明类型意味着外部只能知道它的名字,而不能直接访问其内部结构
export opaque type Task = {
id: number,
callback: Callback | null,
priorityLevel: PriorityLevel,
startTime: number,
expirationTime: number,
sortIndex: number,
isQueued?: boolean,
};
id: number
:每个任务都有一个唯一的id
,类型为number
,用于标识任务。callback: Callback | null
:callback
是任务要执行的回调函数,类型为Callback
或者null
。Callback
应该是在其他地方定义的一种函数类型。priorityLevel: PriorityLevel
:priorityLevel
表示任务的优先级,PriorityLevel
应该是一个自定义的类型,用于表示不同的优先级等级。startTime: number
:startTime
是任务的开始时间,类型为number
,通常可能是一个时间戳。expirationTime: number
:expirationTime
是任务的过期时间,类型为number
,同样可能是一个时间戳。当任务超过这个时间还未执行,可能会有相应的处理逻辑。sortIndex: number
:sortIndex
是用于排序的索引,类型为number
。在任务调度时,可能会根据这个索引对任务进行排序。isQueued?: boolean
:isQueued
是一个可选属性,类型为boolean
,用于表示任务是否已经被加入到任务队列中。
工具函数之 performWorkUntilDeadline
performWorkUntilDeadline
函数的主要作用是在浏览器的消息循环中持续执行工作任务,直到没有更多的工作或者达到某个截止时间。它会在当前浏览器任务中不断尝试刷新工作,若还有剩余工作则安排下一次消息事件继续执行,若没有剩余工作则停止消息循环。
const performWorkUntilDeadline = () => {
// isMessageLoopRunning 是一个布尔标志,用于判断消息循环是否正在运行。只有当消息循环正在运行时,才会执行后续的工作。
if (isMessageLoopRunning) {
const currentTime = getCurrentTime();
// Keep track of the start time so we can measure how long the main thread
// has been blocked.
startTime = currentTime;
// hasMoreWork 初始化为 true,用于标记是否还有更多的工作需要执行。
let hasMoreWork = true;
try {
// 调用 flushWork(currentTime) 函数来尝试刷新工作。flushWork 函数会根据当前时间处理调度任务,并返回一个布尔值,表示是否还有剩余的工作。
hasMoreWork = flushWork(currentTime);
} finally {
if (hasMoreWork) {
// 如果 hasMoreWork 为 true,说明还有工作未完成,调用 schedulePerformWorkUntilDeadline() 函数安排下一次消息事件,以便继续执行剩余的工作。
schedulePerformWorkUntilDeadline();
} else {
// 如果 hasMoreWork 为 false,说明所有工作都已完成,将 isMessageLoopRunning 标志设置为 false,表示消息循环停止运行。
isMessageLoopRunning = false;
}
}
}
};
const getCurrentTime =
// $FlowFixMe[method-unbinding]
typeof performance === 'object' && typeof performance.now === 'function'
? () => performance.now()
: () => Date.now();
performance.now():基准点是页面的导航开始时间(即页面加载时刻),返回的是当前时间距离页面加载的时间差。它的值通常较小,且不容易受到系统时钟调整的影响。
Date.now():基准点是1970年1月1日00:00:00 UTC(Unix纪元),返回的是自该时刻以来的毫秒数。它的值是一个非常大的数字,可能会受到系统时间变更的影响。
工具函数之 schedulePerformWorkUntilDeadline
实现 schedulePerformWorkUntilDeadline
函数,该函数的作用是安排 performWorkUntilDeadline
函数尽快执行。代码会根据当前环境支持的特性,选择不同的异步调度方式来实现这一功能,确保在不同环境下都能高效地调度任务。
使用优先顺序: setImmediate > MessageChannel > setTimeout
let schedulePerformWorkUntilDeadline;
if (typeof localSetImmediate === 'function') {
schedulePerformWorkUntilDeadline = () => {
localSetImmediate(performWorkUntilDeadline);
};
} else if (typeof MessageChannel !== 'undefined') {
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
schedulePerformWorkUntilDeadline = () => {
port.postMessage(null);
};
} else {
// We should only fallback here in non-browser environments.
schedulePerformWorkUntilDeadline = () => {
// $FlowFixMe[not-a-function] nullable value
localSetTimeout(performWorkUntilDeadline, 0);
};
}
const localSetTimeout = typeof setTimeout === 'function' ? setTimeout : null;
const localClearTimeout =
typeof clearTimeout === 'function' ? clearTimeout : null;
const localSetImmediate =
typeof setImmediate !== 'undefined' ? setImmediate : null; // IE and Node.js + jsdom
setImmediate 不再推荐使用。用来把一些需要长时间运行的操作放在一个回调函数里,在浏览器完成后面的其他语句后,就立刻执行这个回调函数。
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/setImmediate
https://developer.mozilla.org/zh-CN/docs/Web/API/MessageChannel
例子:关于MessageChannel的使用
// 创建一个新的消息通道
const channel = new MessageChannel();
// port1 和 port2 是两个相互连接的端口
const port1 = channel.port1;
const port2 = channel.port2;
// 在 port1 上设置消息监听
port1.onmessage = (event) => {
console.log('port1 收到消息:', event.data);
};
// 通过 port2 发送消息
port2.postMessage('Hello from port2!');
// 也可以在 port2 上设置监听
port2.onmessage = (event) => {
console.log('port2 收到消息:', event.data);
};
// 通过 port1 发送消息
port1.postMessage('Hello from port1!');
例子:与iframe的通信
const iframe = document.querySelector('iframe');
const channel = new MessageChannel();
// 将 port2 传递给 iframe
iframe.contentWindow.postMessage('init', '*', [channel.port2]);
// 在主窗口使用 port1
channel.port1.onmessage = (event) => {
console.log('来自 iframe 的消息:', event.data);
};
工具函数之 peek
取出堆顶元素,没有则返回null。
function peek<T: Node>(heap: Heap<T>): T | null {
return heap.length === 0 ? null : heap[0];
}
type Heap<T: Node> = Array<T>;
type Node = {
id: number,
sortIndex: number,
...
};
工具函数之 push
将一个node参数加入到数组中,并重新排序。
function push<T: Node>(heap: Heap<T>, node: T): void {
const index = heap.length;
heap.push(node);
// 重新调整堆
siftUp(heap, node, index);
}
工具函数之 pop
pop
函数用于从最小堆(小顶堆)中移除并返回堆顶元素(即堆中最小的元素),同时维护堆的性质。小顶堆是一种特殊的完全二叉树,其中每个节点的值都小于或等于其子节点的值。该函数会先检查堆是否为空,如果为空则返回 null
,否则移除堆顶元素,并将堆的最后一个元素移动到堆顶,然后通过 siftDown
操作重新调整堆,使其恢复最小堆的性质。
function pop<T: Node>(heap: Heap<T>): T | null {
if (heap.length === 0) {
return null;
}
// 取出第一个元素
const first = heap[0];
// 删除并返回数组的最后一个元素,改变数组长度
const last = heap.pop();
// 如何删除的 元素不是第一个元素,便将最后一个元素设置为第一个元素
if (last !== first) {
heap[0] = last;
// 重新调整堆
siftDown(heap, last, 0);
}
return first;
}
工具函数之 siftUp
实现了最小堆(小顶堆)中的 siftUp
操作。最小堆是一种特殊的完全二叉树,树中每个节点的值都小于或等于其子节点的值。siftUp
操作主要用于在向堆中插入新元素或者更新某个元素的值后,通过不断将该元素与其父节点比较并交换位置,使堆重新满足最小堆的性质。
function siftUp<T: Node>(heap: Heap<T>, node: T, i: number): void {
let index = i;
while (index > 0) {
// 无符号右移
const parentIndex = (index - 1) >>> 1;
const parent = heap[parentIndex];
if (compare(parent, node) > 0) {
// The parent is larger. Swap positions.
heap[parentIndex] = node;
heap[index] = parent;
index = parentIndex;
} else {
// The parent is smaller. Exit.
return;
}
}
}
siftDown
函数实现了最小堆(小顶堆)的下沉操作。在最小堆这种数据结构中,每个节点的值都小于或等于其子节点的值。当堆中的某个节点的值发生变化(通常是增大),或者新插入一个节点到堆的底部后需要将其调整到合适位置时,就需要使用下沉操作来维护堆的性质。该函数接收一个堆数组 heap
、一个节点 node
以及该节点在堆中的初始索引 i
,通过不断比较节点与其子节点的大小,并进行交换,将节点下沉到合适的位置。
function siftDown<T: Node>(heap: Heap<T>, node: T, i: number): void {
let index = i;
const length = heap.length;
// 无符号右移
const halfLength = length >>> 1;
while (index < halfLength) {
const leftIndex = (index + 1) * 2 - 1;
const left = heap[leftIndex];
const rightIndex = leftIndex + 1;
const right = heap[rightIndex];
// If the left or right node is smaller, swap with the smaller of those.
if (compare(left, node) < 0) {
if (rightIndex < length && compare(right, left) < 0) {
heap[index] = right;
heap[rightIndex] = node;
index = rightIndex;
} else {
heap[index] = left;
heap[leftIndex] = node;
index = leftIndex;
}
} else if (rightIndex < length && compare(right, node) < 0) {
heap[index] = right;
heap[rightIndex] = node;
index = rightIndex;
} else {
// Neither child is smaller. Exit.
return;
}
}
}
工具函数之 compare
function compare(a: Node, b: Node) {
// Compare sort index first, then task id.
const diff = a.sortIndex - b.sortIndex;
return diff !== 0 ? diff : a.id - b.id;
}
工具函数之 advanceTimers
advanceTimers
函数的主要功能是根据当前时间 currentTime
对定时器队列 timerQueue
进行处理。它会遍历定时器队列中的任务,根据任务的状态(回调函数是否为空、是否到达开始时间)来决定是移除任务、将任务从定时器队列转移到任务队列 taskQueue
中,还是停止处理。
function advanceTimers(currentTime: number) {
// 取出定时队列中的第一个任务
let timer = peek(timerQueue);
//遍历定时器队列
while (timer !== null) {
if (timer.callback === null) {
// 移除第一个任务
pop(timerQueue);
} else if (timer.startTime <= currentTime) {
// 过期了 移除任务
pop(timerQueue);
timer.sortIndex = timer.expirationTime;
// 添加任务
push(taskQueue, timer);
} else {
// Remaining timers are pending.
return;
}
// 取出任务
timer = peek(timerQueue);
}
}
工具函数之 requestHostTimeout
function requestHostTimeout(
callback: (currentTime: number) => void,
ms: number,
) {
// 延时执行setTimeout
taskTimeoutID = localSetTimeout(() => {
callback(getCurrentTime());
}, ms);
}
processRootScheduleInMicrotask
processRootScheduleInMicrotask
函数是 React 调度系统中处理微任务阶段根节点调度的核心函数。其主要作用是在微任务中遍历所有已调度的根节点(FiberRoot
),为每个根节点安排任务,并根据任务状态决定是否保留根节点在调度列表中。同时,该函数会处理同步过渡车道和同步工作,确保高优先级任务(如同步任务)能及时执行。
function processRootScheduleInMicrotask() {
// 标记是否已调度微任务(初始为 false)
didScheduleMicrotask = false;
// 标记是否存在待处理的同步工作
mightHavePendingSyncWork = false;
// 存储同步过渡车道,初始化为 NoLanes。
let syncTransitionLanes = NoLanes;
// 处理过渡车道, 过渡车道:用于标记需要紧急处理的状态过渡(如用户输入触发的更新)。
if (currentEventTransitionLane !== NoLane) {
// 判断是否需要提前处理过渡任务(如同步任务优先执行)。
if (shouldAttemptEagerTransition()) {
syncTransitionLanes = currentEventTransitionLane;
}
// 重置当前事件过渡车道
currentEventTransitionLane = NoLane;
}
const currentTime = now();
// 记录前一个节点
let prev = null;
// 从调度列表头部开始遍历
let root = firstScheduledRoot;//firstScheduledRoot就是fiberRoot
// 遍历所有已调度的根节点。
while (root !== null) {
const next = root.next;// next = null
// 对于每个根节点,调用 scheduleTaskForRootDuringMicrotask 函数为其安排任务,得到 nextLanes。
const nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime);
if (nextLanes === NoLane) {
// 根节点已无待处理任务,从调度列表中移除
root.next = null;
if (prev === null) {
firstScheduledRoot = next;// 更新列表头部
} else {
prev.next = next;
}
if (next === null) {
lastScheduledRoot = prev;// 更新列表尾部
}
} else {
// This root still has work. Keep it in the list.
prev = root;
if (syncTransitionLanes !== NoLanes || includesSyncLane(nextLanes)) {
mightHavePendingSyncWork = true;
}
}
root = next;
}
// 刷新同步工作
flushSyncWorkAcrossRoots_impl(syncTransitionLanes, false);// syncTransitionLanes = 0
}
flushSyncWorkAcrossRoots_impl
flushSyncWorkAcrossRoots_impl
函数的主要目的是在多个 FiberRoot
上刷新同步工作。它会遍历所有已调度的 FiberRoot
,检查每个根节点是否有等待执行的同步工作,如果有则调用 performSyncWorkOnRoot
函数执行这些同步工作。
function flushSyncWorkAcrossRoots_impl(
syncTransitionLanes: Lanes | Lane,
onlyLegacy: boolean,// false
) {
// isFlushingWork 是一个标志,用于防止函数重入。如果当前已经在刷新工作,函数会直接返回,避免重复操作。
if (isFlushingWork) {
return;
}
// mightHavePendingSyncWork 用于快速判断是否可能存在待执行的同步工作。如果为 false,则说明没有同步工作需要处理,函数直接返回。
if (!mightHavePendingSyncWork) {
// Fast path. There's no sync work to do.
return;
}
// didPerformSomeWork 用于记录本次循环中是否执行了同步工作。
let didPerformSomeWork;
// isFlushingWork 置为 true,表示开始进行刷新工作。
isFlushingWork = true;
// do-while 循环会持续执行,直到本次循环中没有执行任何同步工作。
do {
didPerformSomeWork = false;
let root = firstScheduledRoot;
while (root !== null) {
if (onlyLegacy && (disableLegacyMode || root.tag !== LegacyRoot)) {
// Skip non-legacy roots.
} else {
if (syncTransitionLanes !== NoLanes) {
const nextLanes = getNextLanesToFlushSync(root, syncTransitionLanes);
if (nextLanes !== NoLanes) {
// This root has pending sync work. Flush it now.
didPerformSomeWork = true;
performSyncWorkOnRoot(root, nextLanes);
}
} else {
const workInProgressRoot = getWorkInProgressRoot();
const workInProgressRootRenderLanes =
getWorkInProgressRootRenderLanes();
const nextLanes = getNextLanes(
root,
root === workInProgressRoot
? workInProgressRootRenderLanes
: NoLanes,
);
if (
includesSyncLane(nextLanes) &&
!checkIfRootIsPrerendering(root, nextLanes)
) {
// This root has pending sync work. Flush it now.
didPerformSomeWork = true;
performSyncWorkOnRoot(root, nextLanes);
}
}
}
root = root.next;
}
} while (didPerformSomeWork);
// 将 isFlushingWork 置为 false,表示刷新工作结束。
isFlushingWork = false;
}
performSyncWorkOnRoot
performSyncWorkOnRoot
函数是 React 中用于执行同步工作的入口函数,主要处理那些不经过调度器(Scheduler)的同步任务。它会先尝试刷新被动副作用(passive effects),根据刷新结果决定是否继续执行后续的同步工作,最后调用 performWorkOnRoot
函数来真正执行根节点上的同步工作。
参数说明
root: FiberRoot
:表示 React 应用的根节点,代表整个 React 应用的Fiber
树的根。lanes: Lanes
:渲染优先级车道,用于标识当前同步工作的优先级。
function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes) {
// 调用flushPassiveEffects函数尝试刷新被动副作用,被动副作用通常是指 useEffect 等钩子中异步执行的副作用操作。
const didFlushPassiveEffects = flushPassiveEffects();
// 如果 didFlushPassiveEffects 为 true,说明成功刷新了被动副作用,此时函数直接返回 null,退出到根调度器的外部工作循环,以便重新计算优先级。
if (didFlushPassiveEffects) {
return null;
}
// 设置 forceSync 为 true,表示强制进行同步渲染。
const forceSync = true;
// 执行根节点上的同步工作
performWorkOnRoot(root, lanes, forceSync);
}
performWorkOnRoot
performWorkOnRoot
函数是 React 渲染流程中的关键函数,主要负责在指定的 FiberRoot
上执行渲染工作。它会根据不同的条件判断是否使用时间切片(time-slicing)机制,然后调用相应的渲染函数进行渲染,最后确保根节点被正确调度。
参数说明
root: FiberRoot
:React 应用的根节点,代表整个 React 应用的Fiber
树的根。lanes: Lanes
:渲染优先级车道,用于标识当前渲染任务的优先级。forceSync: boolean
:一个布尔值,指示是否强制同步渲染。
function performWorkOnRoot(
root: FiberRoot,
lanes: Lanes,
forceSync: boolean,// true
): void {
// executionContext 是一个表示当前执行上下文的变量,RenderContext 和 CommitContext 分别代表渲染上下文和提交上下文。通过按位与操作检查当前是否处于渲染或提交上下文中,如果是,则抛出错误,因为不应该在已经工作的情况下再次调用此函数。
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
throw new Error('Should not already be working.');
}
// 判断是否使用时间切片
const shouldTimeSlice =
// !forceSync:如果不是强制同步渲染。
(!forceSync &&
// !includesBlockingLane(lanes):当前渲染任务的优先级车道中不包含阻塞车道。
!includesBlockingLane(lanes) &&
// !includesExpiredLane(root, lanes):根节点的渲染任务中不包含过期的车道。
!includesExpiredLane(root, lanes)) ||
// enableSiblingPrerendering:是否启用了兄弟节点预渲染功能。
// checkIfRootIsPrerendering(root, lanes):检查根节点是否正在预渲染。
(enableSiblingPrerendering && checkIfRootIsPrerendering(root, lanes));
// 执行渲染
let exitStatus = shouldTimeSlice
// 调用 renderRootConcurrent 函数进行并发渲染,即使用时间切片机制,将渲染工作分成多个小任务,在浏览器的空闲时间执行,避免阻塞主线程。
? renderRootConcurrent(root, lanes)
// 调用 renderRootSync 函数进行同步渲染,一次性完成渲染工作。
: renderRootSync(root, lanes, true);
let renderWasConcurrent = shouldTimeSlice;
// 省略代码。。。 处理渲染结果
// 确保根节点被调度
ensureRootIsScheduled(root);
}
do {
// 当 exitStatus 为 RootInProgress 时,表示渲染阶段仍在进行中。
if (exitStatus === RootInProgress) {
// 启用了兄弟节点预渲染(enableSiblingPrerendering)
// 当前根节点正在预渲染(workInProgressRootIsPrerendering)
// 且不使用时间切片(!shouldTimeSlice)
if (
enableSiblingPrerendering &&
workInProgressRootIsPrerendering &&
!shouldTimeSlice
) {
const didAttemptEntireTree = false;
markRootSuspended(root, lanes, NoLane, didAttemptEntireTree);
}
// 跳出循环
break;
// 当 exitStatus 为 RootDidNotComplete 时,表示渲染过程在未完成整个树的情况下退出,这可能是由于特殊情况导致的。
} else if (exitStatus === RootDidNotComplete) {
const didAttemptEntireTree = !workInProgressRootDidSkipSuspendedSiblings;
// 将根节点标记为暂停状态。
markRootSuspended(root, lanes, NoLane, didAttemptEntireTree);
} else {
const finishedWork: Fiber = (root.current.alternate: any);
if (
renderWasConcurrent &&
// 对比前后是否一致
!isRenderConsistentWithExternalStores(finishedWork)
) {
// 执行同步渲染
exitStatus = renderRootSync(root, lanes, false);
// We assume the tree is now consistent because we didn't yield to any
// concurrent events.
renderWasConcurrent = false;
// 跳出循环,重新判断exitStatus
continue;
}
// Check if something threw
if (
(disableLegacyMode || root.tag !== LegacyRoot) &&
exitStatus === RootErrored // 渲染出错
) {
const lanesThatJustErrored = lanes;
// 获取同步重试车道
const errorRetryLanes = getLanesToRetrySynchronouslyOnError(
root,
lanesThatJustErrored,
);
// 重试车道
if (errorRetryLanes !== NoLanes) {
lanes = errorRetryLanes;
// 执行恢复错误
exitStatus = recoverFromConcurrentError(
root,
lanesThatJustErrored,
errorRetryLanes,
);
renderWasConcurrent = false;
// Need to check the exit status again.
if (exitStatus !== RootErrored) {
continue;
}
}
}
// 致命错误
if (exitStatus === RootFatalErrored) {
// 准备新的执行栈
prepareFreshStack(root, NoLanes);
const didAttemptEntireTree = true;
markRootSuspended(root, lanes, NoLane, didAttemptEntireTree);
break;
}
// 调用 finishConcurrentRender 函数完成并发渲染。
finishConcurrentRender(root, exitStatus, finishedWork, lanes);
}
break;
} while (true);
renderRootConcurrent
renderRootConcurrent
函数是 React 中进行并发渲染的核心函数。它负责处理根节点的渲染工作,包括判断是否需要重新准备渲染栈、处理不同的暂停原因(如数据暂停、错误暂停等)、执行工作循环以及处理渲染完成后的操作。
function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
// 调度器
const prevDispatcher = pushDispatcher(root.containerInfo);
// 异步调度器
const prevAsyncDispatcher = pushAsyncDispatcher();
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
workInProgressTransitions = getTransitionsForLanes(root, lanes);
resetRenderTimer();
prepareFreshStack(root, lanes);
} else {
workInProgressRootIsPrerendering = checkIfRootIsPrerendering(root, lanes);
}
// 省略代码 do 。。。while
// 重置
resetContextDependencies();
// 恢复之前的调度器与执行上下文
popDispatcher(prevDispatcher);
popAsyncDispatcher(prevAsyncDispatcher);
executionContext = prevExecutionContext;
// Check if the tree has completed.
if (workInProgress !== null) {
// 如果 workInProgress 不为 null,表示渲染未完成,返回 RootInProgress。
return RootInProgress;
} else {
// Completed the tree.
// 渲染完成,重置正在进行的根节点和渲染车道,完成并发更新队列 finishQueueingConcurrentUpdates,并返回根节点的退出状态 workInProgressRootExitStatus。
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
// It's safe to process the queue now that the render phase is complete.
finishQueueingConcurrentUpdates();
// Return the final exit status.
return workInProgressRootExitStatus;
}
}
outer: do {
try {
if (
workInProgressSuspendedReason !== NotSuspended &&
workInProgress !== null
) {
// The work loop is suspended. We need to either unwind the stack or
// replay the suspended component.
const unitOfWork = workInProgress;
const thrownValue = workInProgressThrownValue;
// 处理不同的暂停原因
resumeOrUnwind: switch (workInProgressSuspendedReason) {
// 处理错误暂停,重置暂停原因和抛出的值,并调用 throwAndUnwindWorkLoop 函数。
case SuspendedOnError: {
// Unwind then continue with the normal work loop.
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
throwAndUnwindWorkLoop(
root,
unitOfWork,
thrownValue,
SuspendedOnError,
);
break;
}
// 处理数据错误
case SuspendedOnData: {
const thenable: Thenable<mixed> = (thrownValue: any);
if (isThenableResolved(thenable)) {
// 若已解析则重置暂停原因和抛出的值
// The data resolved. Try rendering the component again.
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
replaySuspendedUnitOfWork(unitOfWork);
break;
}
// 设置数据解析后的回调函数 onResolution
const onResolution = () => {
// Check if the root is still suspended on this promise.
if (
workInProgressSuspendedReason === SuspendedOnData &&
workInProgressRoot === root
) {
// Mark the root as ready to continue rendering.
workInProgressSuspendedReason = SuspendedAndReadyToContinue;
}
ensureRootIsScheduled(root);
};
thenable.then(onResolution, onResolution);
break outer;
}
// 设置暂停原因为已准备好继续。
case SuspendedOnImmediate: {
workInProgressSuspendedReason = SuspendedAndReadyToContinue;
break outer;
}
// 设置暂停原因为已准备好继续。
case SuspendedOnInstance: {
workInProgressSuspendedReason =
SuspendedOnInstanceAndReadyToContinue;
break outer;
}
// 检查数据是否已解析
case SuspendedAndReadyToContinue: {
const thenable: Thenable<mixed> = (thrownValue: any);
if (isThenableResolved(thenable)) {
// The data resolved. Try rendering the component again.
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
replaySuspendedUnitOfWork(unitOfWork);
} else {
// Otherwise, unwind then continue with the normal work loop.
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
throwAndUnwindWorkLoop(
root,
unitOfWork,
thrownValue,
SuspendedAndReadyToContinue,
);
}
break;
}
case SuspendedOnInstanceAndReadyToContinue: {
let resource: null | Resource = null;
// 根据 workInProgress.tag 的值处理不同类型的暂停,检查实例是否已准备好,若已准备好则更新工作进度
switch (workInProgress.tag) {
case HostHoistable: {
resource = workInProgress.memoizedState;
}
// intentional fallthrough
case HostComponent:
case HostSingleton: {
const hostFiber = workInProgress;
const type = hostFiber.type;
const props = hostFiber.pendingProps;
const isReady = resource
? preloadResource(resource)
: preloadInstance(type, props);
if (isReady) {
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
const sibling = hostFiber.sibling;
if (sibling !== null) {
workInProgress = sibling;
} else {
const returnFiber = hostFiber.return;
if (returnFiber !== null) {
workInProgress = returnFiber;
completeUnitOfWork(returnFiber);
} else {
workInProgress = null;
}
}
break resumeOrUnwind;
}
break;
}
default: {
break;
}
}
// Otherwise, unwind then continue with the normal work loop.
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
throwAndUnwindWorkLoop(
root,
unitOfWork,
thrownValue,
SuspendedOnInstanceAndReadyToContinue,
);
break;
}
case SuspendedOnDeprecatedThrowPromise: {
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
throwAndUnwindWorkLoop(
root,
unitOfWork,
thrownValue,
SuspendedOnDeprecatedThrowPromise,
);
break;
}
case SuspendedOnHydration: {
resetWorkInProgressStack();
workInProgressRootExitStatus = RootDidNotComplete;
break outer;
}
default: {
throw new Error(
'Unexpected SuspendedReason. This is a bug in React.',
);
}
}
}
// 执行并发工作循环
workLoopConcurrent();
break;
} catch (thrownValue) {
handleThrow(root, thrownValue);
}
} while (true);
renderRootSync
renderRootSync
函数是 React 渲染流程中用于同步渲染根节点的核心函数。它会设置执行上下文,根据根节点和渲染优先级车道的变化重置渲染栈,然后开始同步渲染工作。在渲染过程中,会处理可能出现的暂停情况,最后恢复执行上下文并返回渲染的退出状态。
参数说明
root: FiberRoot
:表示 React 应用的根节点,是整个Fiber
树的根,包含了应用的各种状态和信息。lanes: Lanes
:渲染优先级车道,用于标识当前渲染任务的优先级,不同的优先级车道决定了渲染任务的执行顺序。shouldYieldForPrerendering: boolean
:一个布尔值,指示是否应该为预渲染而让出控制权,在当前代码中未被使用,但可能在后续逻辑中有作用。
function renderRootSync(
root: FiberRoot,
lanes: Lanes,
shouldYieldForPrerendering: boolean,
): RootExitStatus {
// 当前的执行上下文
const prevExecutionContext = executionContext;
// 将执行上下文设置为渲染上下文,表示当前处于渲染阶段。
executionContext |= RenderContext;
// 当前的调度器
const prevDispatcher = pushDispatcher(root.containerInfo);
// 异步调度器
const prevAsyncDispatcher = pushAsyncDispatcher();
// workInProgressRootRenderLanes代表当前正在处理的渲染优先级车道
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
// 获取当前渲染优先级车道对应的过渡信息。
workInProgressTransitions = getTransitionsForLanes(root, lanes);
// 准备一个新的渲染栈,重置渲染状态。
prepareFreshStack(root, lanes);
}
// 用于标记在渲染过程中是否在 shell 中发生了暂停。
let didSuspendInShell = false;
// 存储渲染的退出状态
let exitStatus = workInProgressRootExitStatus;
// 省略代码do..while
// 如果在渲染过程中发生了暂停(didSuspendInShell 为 true),则增加根节点的 shellSuspendCounter 计数器。
if (didSuspendInShell) {
root.shellSuspendCounter++;
}
// 重置上下文依赖,确保上下文状态的一致性。
resetContextDependencies();
// 恢复之前保存的执行上下文。
executionContext = prevExecutionContext;
// 恢复之前保存的调度器。
popDispatcher(prevDispatcher);
// 恢复之前保存的异步调度器。
popAsyncDispatcher(prevAsyncDispatcher);
if (workInProgress !== null) {
// Did not complete the tree. This can happen if something suspended in
// the shell.
} else {
// Normal case. We completed the whole tree.
// Set this to null to indicate there's no in-progress render.
workInProgressRoot = null;
// 表示没有正在进行的渲染任务
workInProgressRootRenderLanes = NoLanes;
// 调用 finishQueueingConcurrentUpdates() 函数处理并发更新队列。
finishQueueingConcurrentUpdates();
}
return exitStatus;
}
workLoopConcurrent
workLoopConcurrent
函数是 React 并发模式下执行工作循环的核心函数。它的主要职责是在存在未完成的工作(workInProgress
)且不需要让步(shouldYield
返回 false
)的情况下,持续执行单元工作(performUnitOfWork
),从而推进渲染流程,处理 Fiber
树中的任务。
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
// $FlowFixMe[incompatible-call] found when upgrading Flow
performUnitOfWork(workInProgress);
}
}
workLoopSync
workLoopSync
函数是 React 渲染流程中的一个关键函数,它实现了同步工作循环。在这个循环里,会持续处理 workInProgress
指向的 Fiber
节点,直到 workInProgress
变为 null
,也就是没有更多的 Fiber
节点需要处理为止。
function workLoopSync() {
// Perform work without checking if we need to yield between fiber.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
finishConcurrentRender
finishConcurrentRender
函数是 React 并发渲染流程中用于处理渲染完成状态的核心函数。它根据渲染的退出状态(exitStatus
)执行不同的操作,包括处理渲染进行中、致命错误、延迟暂停、普通错误、已完成等状态。函数还会更新根节点(root
)的相关信息,如已完成的工作(finishedWork
)和已完成的车道(finishedLanes
),并根据条件决定是否进行提交操作(commitRootWhenReady
)或标记根节点为暂停状态(markRootSuspended
)
函数参数含义
root
:类型为FiberRoot
,代表 React 应用的根节点,包含了整个应用的渲染信息。exitStatus
:类型为RootExitStatus
,表示渲染的退出状态,用于判断渲染过程是完成、出错、暂停等情况。finishedWork
:类型为Fiber
,是完成渲染的Fiber
节点树。lanes
:类型为Lanes
,表示渲染的优先级车道,用于区分不同类型和优先级的渲染任务。
function finishConcurrentRender(
root: FiberRoot,
exitStatus: RootExitStatus,
finishedWork: Fiber,
lanes: Lanes,
) {
// 初始化渲染结束时间
let renderEndTime = 0;
// 根据退出状态执行不同的操作
switch (exitStatus) {
// 渲染仍在进行中或者出现致命错误
case RootInProgress:
case RootFatalErrored: {
throw new Error('Root did not complete. This is a bug in React.');
}
case RootSuspendedWithDelay: {
// 如果渲染是延迟暂停的,并且 lanes 中只包含过渡任务(通过 includesOnlyTransitions 函数判断),则标记根节点为暂停状态(调用 markRootSuspended 函数),并直接返回。如果不满足该条件,则继续执行后续代码。
if (includesOnlyTransitions(lanes)) {
const didAttemptEntireTree =
!workInProgressRootDidSkipSuspendedSiblings;
markRootSuspended(
root,
lanes,
workInProgressDeferredLane,
didAttemptEntireTree,
);
return;
}
// Commit the placeholder.
break;
}
case RootErrored: {
workInProgressRootRecoverableErrors = null;
break;
}
case RootSuspended:
case RootCompleted: {
break;
}
default: {
throw new Error('Unknown root exit status.');
}
}
// 更新节点信息
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
// 若包含重试车道
if (
includesOnlyRetries(lanes) &&
(alwaysThrottleRetries || exitStatus === RootSuspended)
) {
// 计算距离超时时间
const msUntilTimeout =
globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now();
if (msUntilTimeout > 10) {
const didAttemptEntireTree =
!workInProgressRootDidSkipSuspendedSiblings;
// 标记根节点为暂停状态
markRootSuspended(
root,
lanes,
workInProgressDeferredLane,
didAttemptEntireTree,
);
// 获取下一个车道(nextLanes)
const nextLanes = getNextLanes(root, NoLanes);
if (nextLanes !== NoLanes) {
return;
}
// 用 scheduleTimeout 函数在 msUntilTimeout 毫秒后执行 commitRootWhenReady 函数进行提交操作,并返回。
root.timeoutHandle = scheduleTimeout(
commitRootWhenReady.bind(
null,
root,
finishedWork,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
workInProgressRootDidIncludeRecursiveRenderUpdate,
lanes,
workInProgressDeferredLane,
workInProgressRootInterleavedUpdatedLanes,
workInProgressSuspendedRetryLanes,
workInProgressRootDidSkipSuspendedSiblings,
THROTTLED_COMMIT,
renderStartTime,
renderEndTime,
),
msUntilTimeout,
);
return;
}
}
// 执行提交操作
commitRootWhenReady(
root,
finishedWork,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
workInProgressRootDidIncludeRecursiveRenderUpdate,
lanes,
workInProgressDeferredLane,
workInProgressRootInterleavedUpdatedLanes,
workInProgressSuspendedRetryLanes,
workInProgressRootDidSkipSuspendedSiblings,
IMMEDIATE_COMMIT,
renderStartTime,
renderEndTime,
);
}
commitRootWhenReady
commitRootWhenReady
函数的主要功能是在满足一定条件时提交根节点的渲染结果。它会检查 Fiber
树的子树标志,判断是否需要暂停提交。如果需要暂停,会启动暂停提交流程,累积与 Suspense
相关的提交信息,并等待渲染器准备好提交。如果不需要暂停,则直接调用 commitRoot
函数进行提交。
函数参数含义
- root,根节点 fiber root。
- finishedWork,已完成的 root fiber。
- recoverableErrors,可恢复错误。
- transitions,过渡信息。
- didIncludeRenderPhaseUpdate,是否包含渲染阶段更新。
- lanes,车道。
- updatedLanes,更新的车道。
- suspendedRetryLanes,暂停重试车道。
- suspendedCommitReason,暂停提交原因。
- completedRenderStartTime,完成渲染的开始时间。
- completedRenderEndTime,完成渲染的结束时间。
function commitRootWhenReady(
root: FiberRoot,
finishedWork: Fiber,
recoverableErrors: Array<CapturedValue<mixed>> | null,
transitions: Array<Transition> | null,
didIncludeRenderPhaseUpdate: boolean,
lanes: Lanes,
spawnedLane: Lane,
updatedLanes: Lanes,
suspendedRetryLanes: Lanes,
didSkipSuspendedSiblings: boolean,
suspendedCommitReason: SuspendedCommitReason, // Profiling-only
completedRenderStartTime: number, // Profiling-only
completedRenderEndTime: number, // Profiling-only
) {
const BothVisibilityAndMaySuspendCommit = Visibility | MaySuspendCommit;
// 获取 finishedWork(已完成的 Fiber 节点)的子树标志,用于判断子树是否需要暂停提交。
const subtreeFlags = finishedWork.subtreeFlags;
// 判断是否需要暂停提交
if (
subtreeFlags & ShouldSuspendCommit ||
(subtreeFlags & BothVisibilityAndMaySuspendCommit) ===
BothVisibilityAndMaySuspendCommit
) {
// 启动暂停提交的流程,设置相关状态。
startSuspendingCommit();
// 收集需要暂停提交的 Fiber 节点信息,可能是包含 Suspense 组件的部分。
accumulateSuspenseyCommit(finishedWork);
// 返回一个函数,用于在条件满足时调度提交操作。如果返回 null,表示无法暂停,需立即提交。
const schedulePendingCommit = waitForCommitToBeReady();
if (schedulePendingCommit !== null) {
// 调度延迟提交
root.cancelPendingCommit = schedulePendingCommit(
commitRoot.bind(
null,
root,
recoverableErrors,
transitions,
didIncludeRenderPhaseUpdate,
spawnedLane,
updatedLanes,
suspendedRetryLanes,
SUSPENDED_COMMIT,
completedRenderStartTime,
completedRenderEndTime,
),
);
const didAttemptEntireTree = !didSkipSuspendedSiblings;
markRootSuspended(root, lanes, spawnedLane, didAttemptEntireTree);
return;
}
}
// Otherwise, commit immediately.;
// 如果不需要暂停提交,直接调用 commitRoot 函数进行根节点的提交操作。
commitRoot(
root,
recoverableErrors,
transitions,
didIncludeRenderPhaseUpdate,
spawnedLane,
updatedLanes,
suspendedRetryLanes,
suspendedCommitReason,
completedRenderStartTime,
completedRenderEndTime,
);
}
工具函数之 pickArbitraryLaneIndex
function pickArbitraryLaneIndex(lanes: Lanes) {
return 31 - clz32(lanes);
}
const clz32: (x: number) => number = Math.clz32
? Math.clz32
: clz32Fallback;
const log = Math.log;
const LN2 = Math.LN2;
function clz32Fallback(x: number): number {
const asUint = x >>> 0;
if (asUint === 0) {
return 32;
}
return (31 - ((log(asUint) / LN2) | 0)) | 0;
}
Math.clz32()
函数返回一个数字在转换成 32 无符号整形数字的二进制形式后,开头的 0 的个数,比如 1000000
转换成 32 位无符号整形数字的二进制形式后是 00000000000011110100001001000000
, 开头的 0 的个数是 12 个,则 Math.clz32(1000000)
返回 12
.
工具函数之 computeExpirationTime
computeExpirationTime
函数的主要作用是根据传入的车道(Lane
)类型和当前时间(currentTime
),计算并返回对应的过期时间。不同类型的车道有不同的过期时间计算逻辑,该函数通过 switch
语句来处理各种车道类型。
function computeExpirationTime(lane: Lane, currentTime: number) {
switch (lane) {
// 处理同步相关车道
case SyncHydrationLane:
case SyncLane:
case InputContinuousHydrationLane:
case InputContinuousLane:
// syncLaneExpirationMs 同步车道的过期时间间隔
return currentTime + syncLaneExpirationMs;
// 处理过渡相关车道
case DefaultHydrationLane:
case DefaultLane:
case TransitionHydrationLane:
case TransitionLane1:
case TransitionLane2:
case TransitionLane3:
case TransitionLane4:
case TransitionLane5:
case TransitionLane6:
case TransitionLane7:
case TransitionLane8:
case TransitionLane9:
case TransitionLane10:
case TransitionLane11:
case TransitionLane12:
case TransitionLane13:
case TransitionLane14:
case TransitionLane15:
// transitionLaneExpirationMs(过渡车道的过期时间间隔
return currentTime + transitionLaneExpirationMs;
// 处理重试车道
case RetryLane1:
case RetryLane2:
case RetryLane3:
case RetryLane4:
return enableRetryLaneExpiration
? currentTime + retryLaneExpirationMs
// NoTimestamp,表示没有设置过期时间。
: NoTimestamp;
case SelectiveHydrationLane:
case IdleHydrationLane:
case IdleLane:
case OffscreenLane:
case DeferredLane:
return NoTimestamp;
default:
return NoTimestamp;
}
}
工具函数之 markRootSuspended
markRootSuspended
函数的主要功能是将 FiberRoot
标记为挂起状态。在 React 的协调过程中,当渲染因为某些原因(如数据未就绪)被挂起时,会调用此函数来更新根节点的状态信息,包括挂起车道、已处理车道、过期时间等,同时处理可能产生的延迟车道。
函数参数含义:
root
:类型为FiberRoot
,代表 React 应用的根节点。suspendedLanes
:类型为Lanes
,表示当前被挂起的车道集合。spawnedLane
:类型为Lane
,表示在挂起过程中产生的延迟车道。didAttemptEntireTree
:类型为boolean
,表示是否尝试渲染整个Fiber
树。
function markRootSuspended(
root: FiberRoot,
suspendedLanes: Lanes,
spawnedLane: Lane,
didAttemptEntireTree: boolean,
) {
// 标记这些车道为挂起状态。
root.suspendedLanes |= suspendedLanes;
// 表示这些车道不再是已处理状态。
root.pingedLanes &= ~suspendedLanes;
// 如果启用了兄弟节点预渲染(enableSiblingPrerendering 为 true)且尝试渲染了整个 Fiber 树(didAttemptEntireTree 为 true)
if (enableSiblingPrerendering && didAttemptEntireTree) {
// 标记这些车道为已预热状态。
root.warmLanes |= suspendedLanes;
}
// 从 root 中获取 expirationTimes 数组,该数组存储了每个车道的过期时间。
const expirationTimes = root.expirationTimes;
let lanes = suspendedLanes;
// 使用 while 循环遍历 suspendedLanes 中的每个车道
while (lanes > 0) {
// 调用 pickArbitraryLaneIndex 函数获取一个车道的索引。
const index = pickArbitraryLaneIndex(lanes);
// 使用左移运算符 << 生成对应的车道掩码。
const lane = 1 << index;
// 将该车道在 expirationTimes 数组中的过期时间设置为 NoTimestamp,表示该车道不再有过期时间。
expirationTimes[index] = NoTimestamp;
// 使用按位与运算符 &= 和按位取反运算符 ~ 从 lanes 中移除已处理的车道。
lanes &= ~lane;
}
// 如果 spawnedLane 不是 NoLane,则调用 markSpawnedDeferredLane 函数处理在挂起过程中产生的延迟车道。
if (spawnedLane !== NoLane) {
markSpawnedDeferredLane(root, spawnedLane, suspendedLanes);
}
}
这里介绍了root.render的过程,后续将继续深入阅读performUnitOfWork函数和commitRoot函数。