React19源码系列之 root.render过程

发布于:2025-05-01 ⋅ 阅读:(46) ⋅ 点赞:(0)


在创建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)中,并调度相应的宿主回调(如浏览器事件循环)来执行任务。其核心逻辑包括:

  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 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 | 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 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
}


网站公告

今日签到

点亮在社区的每一天
去签到