当前位置: 首页 > news >正文

React19源码系列之 root.render过程


在创建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>,// nullcallback: ?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,// 根fiberlane,// 更新车道element,// 子节点container,// fiberRootparentComponent,// nullcallback,);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,// 根节点fiberlane: Lane,// 更新优先级车道element: ReactNodeList,// 需要更新的react元素container: OpaqueRoot,//FiberRootNodeparentComponent: ?React$Component<any, any>,callback: ?Function,
): void {//获取当前节点和子节点的上下文const context = getContextForSubtree(parentComponent);if (container.context === null) {// 将获取到的上下文赋值给 container.contextcontainer.context = context;} else {// 将获取到的上下文赋值给 container.pendingContextcontainer.pendingContext = context;}//创建一个 update 更新对象const update = createUpdate(lane);// 记录update的载荷信息update.payload = {element};// 如果有回调信息,保存callback = callback === undefined ? null : callback;if (callback !== null) {// 存储callbackupdate.callback = callback;}// 将创建好的更新对象 update 加入到根 Fiber 节点的更新队列中。该函数返回根 FiberRootNode 对象。// root 为FiberRootconst 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节点的lanesfiber.lanes = mergeLanes(fiber.lanes, lane); // fiber.lanes | lanesconst alternate = fiber.alternate;if (alternate !== null) {// 更新alternate的lanesalternate.lanes = mergeLanes(alternate.lanes, lane);}
}

工具函数之 getRootForUpdatedFiber 

从一个被更新的 Fiber 节点出发,向上遍历找到对应的根 Fiber 节点(FiberRoot)。

function getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null {
// sourceFiber表示需要查找根节点的起始 Fiber 节点。// 检测是否在已卸载的 Fiber 节点上进行更新。// detectUpdateOnUnmountedFiber(sourceFiber, sourceFiber);// 根fiberlet 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)中,并调度相应的宿主回调(如浏览器事件循环)来执行任务。其核心逻辑包括:

  1. 根据优先级和延迟时间计算任务的开始时间和过期时间。
  2. 将延迟任务加入 timerQueue,非延迟任务加入 taskQueue
  3. 调度宿主超时(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 immediatelytimeout = -1;break;case UserBlockingPriority:// Eventually times outtimeout = userBlockingPriorityTimeout;break;case IdlePriority:// 永不超时// Never times outtimeout = maxSigned31BitInt;break;case LowPriority:// Eventually times outtimeout = lowPriorityTimeout;break;case NormalPriority:default:// Eventually times outtimeout = normalPriorityTimeout;break;}// 过期时间 var expirationTime = startTime + timeout;// 创建新任务对象var newTask: Task = {id: taskIdCounter++, // 任务IDcallback,// 回调函数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,没有则为nullcurrentTask = 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 | nullcallback 是任务要执行的回调函数,类型为 Callback 或者 nullCallback 应该是在其他地方定义的一种函数类型。
  • priorityLevel: PriorityLevelpriorityLevel 表示任务的优先级,PriorityLevel 应该是一个自定义的类型,用于表示不同的优先级等级。
  • startTime: numberstartTime 是任务的开始时间,类型为 number,通常可能是一个时间戳。
  • expirationTime: numberexpirationTime 是任务的过期时间,类型为 number,同样可能是一个时间戳。当任务超过这个时间还未执行,可能会有相应的处理逻辑。
  • sortIndex: numbersortIndex 是用于排序的索引,类型为 number。在任务调度时,可能会根据这个索引对任务进行排序。
  • isQueued?: booleanisQueued 是一个可选属性,类型为 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 valuelocalSetTimeout(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,
) {// 延时执行setTimeouttaskTimeoutID = localSetTimeout(() => {callback(getCurrentTime());}, ms);
}

processRootScheduleInMicrotask函数后面的逻辑,后续再补充。

相关文章:

  • Animate 中HTMLCanvas 画布下的鼠标事件列表(DOM 鼠标)
  • 14、服务端组件:未来魔法预览——React 19 RSC实践
  • 权力结构下的人才价值重构:从 “工具论” 到 “存在论” 的转变​
  • 详解React Fiber架构中,reconcile阶段的具体工作流程
  • 【项目篇之消息序列化】仿照RabbitMQ模拟实现消息队列
  • PostgreSQL psql 命令和常用的 SQL 语句整理
  • WGS84(GPS)、火星坐标系(GCJ02)、百度地图(BD09)坐标系转换Java代码
  • 哈希封装unordered_map和unordered_set的模拟实现
  • 海思dump图原理
  • socket套接字-UDP(中)
  • java Optional
  • 【MQ篇】RabbitMQ之死信交换机!
  • OpenCV 图形API(65)图像结构分析和形状描述符------拟合二维点集的直线函数 fitLine2D()
  • FlinkUpsertKafka深度解析
  • 基础的贝叶斯神经网络(BNN)回归
  • 零基础小白如何上岸数模国奖
  • 大学之大:伦敦政治经济学院2025.4.27
  • 【音视频】FFmpeg过滤器框架分析
  • 人工智能—— K-means 聚类算法
  • Spring Cloud Alibaba 整合 Sentinel:实现微服务高可用防护
  • “五一”假期倒计时,节前错峰出游机票降价四成
  • 朝鲜派兵库尔斯克是否有助于解决乌克兰危机?外交部回应
  • 第1现场|无军用物资!伊朗港口爆炸已遇难40人伤1200人
  • 国家发改委:我国能源进口来源多元,企业减少甚至停止自美能源进口对国内能源供应没有影响
  • 51岁国家移民管理局移民事务服务中心联络部副主任林艺聪逝世
  • 经济日报金观平:统筹国内经济工作和国际经贸斗争