大年初五,快来学 React 优先级模型~

177 阅读16分钟

背景

目前 React 有一套scheduler 的调度器机制,通过scheduler 的调度,可以实现高优先级的任务优先调度,还有可恢复可中断的特性。那么如何实现这一特性呢?其中一个关键的模型就是赛道模型。

事件优先级

React 本身对事件处理进行了包装,所以会存在事件优先级的概念,比如输入事件、点击事件等,都属于离散事件,优先级最高,需要同步执行。拖拽事件,滚动事件属于连续事件优先级,对应的都是连续事件优先级.

// 离散事件优先级, 为同步优先级0b0000000000000000000000000000001
// 比如click,input, change, blur, focus等
export const DiscreteEventPriority: EventPriority = SyncLane;
// 连续事件优先级, 为输入持续优先级0b0000000000000000000000000000100
// 比如touchmove, scroll,dragenter等
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
// 默认事件优先级, 为0b0000000000000000000000000010000
export const DefaultEventPriority: EventPriority = DefaultLane;
// 空闲事件优先级, 为0b0100000000000000000000000000000
export const IdleEventPriority: EventPriority = IdleLane;

lane 优先级

lane 标识的是一个任务的优先级,总共有 31 种,直接看代码:

export const TotalLanes = 31;

export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;

export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;

export const InputContinuousHydrationLane: Lane = /*    */ 0b0000000000000000000000000000010;
export const InputContinuousLane: Lane = /*             */ 0b0000000000000000000000000000100;

export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000000001000;
export const DefaultLane: Lane = /*                     */ 0b0000000000000000000000000010000;

const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000000000000100000;
const TransitionLanes: Lanes = /*                       */ 0b0000000001111111111111111000000;
const TransitionLane1: Lane = /*                        */ 0b0000000000000000000000001000000;
const TransitionLane2: Lane = /*                        */ 0b0000000000000000000000010000000;
const TransitionLane3: Lane = /*                        */ 0b0000000000000000000000100000000;
const TransitionLane4: Lane = /*                        */ 0b0000000000000000000001000000000;
const TransitionLane5: Lane = /*                        */ 0b0000000000000000000010000000000;
const TransitionLane6: Lane = /*                        */ 0b0000000000000000000100000000000;
const TransitionLane7: Lane = /*                        */ 0b0000000000000000001000000000000;
const TransitionLane8: Lane = /*                        */ 0b0000000000000000010000000000000;
const TransitionLane9: Lane = /*                        */ 0b0000000000000000100000000000000;
const TransitionLane10: Lane = /*                       */ 0b0000000000000001000000000000000;
const TransitionLane11: Lane = /*                       */ 0b0000000000000010000000000000000;
const TransitionLane12: Lane = /*                       */ 0b0000000000000100000000000000000;
const TransitionLane13: Lane = /*                       */ 0b0000000000001000000000000000000;
const TransitionLane14: Lane = /*                       */ 0b0000000000010000000000000000000;
const TransitionLane15: Lane = /*                       */ 0b0000000000100000000000000000000;
const TransitionLane16: Lane = /*                       */ 0b0000000001000000000000000000000;

const RetryLanes: Lanes = /*                            */ 0b0000111110000000000000000000000;
const RetryLane1: Lane = /*                             */ 0b0000000010000000000000000000000;
const RetryLane2: Lane = /*                             */ 0b0000000100000000000000000000000;
const RetryLane3: Lane = /*                             */ 0b0000001000000000000000000000000;
const RetryLane4: Lane = /*                             */ 0b0000010000000000000000000000000;
const RetryLane5: Lane = /*                             */ 0b0000100000000000000000000000000;

Schedule优先级

Schedule 总共有6 种优先级,直接看代码:

let ReactPriorityLevels: ReactPriorityLevelsType = {
  ImmediatePriority: 99,
  UserBlockingPriority: 98,
  NormalPriority: 97,
  LowPriority: 96,
  IdlePriority: 95,
  NoPriority: 90,
};

优先级的相互转换

lane优先级转换为事件优先级

function lanesToEventPriority(lanes) {
  var lane = getHighestPriorityLane(lanes);

  if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
    return DiscreteEventPriority;
  }

  if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
    return ContinuousEventPriority;
  }

  if (includesNonIdleWork(lane)) {
    return DefaultEventPriority;
  }

  return IdleEventPriority;
}

事件优先级转换为Schedule优先级

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
    ...
    switch (lanesToEventPriority(nextLanes)) {
      case DiscreteEventPriority:
        schedulerPriorityLevel = ImmediateSchedulerPriority;
        break;
      case ContinuousEventPriority:
        schedulerPriorityLevel = UserBlockingSchedulerPriority;
        break;
      case DefaultEventPriority:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
      case IdleEventPriority:
        schedulerPriorityLevel = IdleSchedulerPriority;
        break;
      default:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
    }
}

如何工作

初始化lane

触发 React 更新一般只有两种方式:

  1. useState
  2. useReducer

下面用 useState 来看一下 lane 初始化的流程

function dispatchSetState(fiber, queue, action) {
  // ... 
  var lane = requestUpdateLane(fiber);

  // 生成一个 update 对象
  var update = {
    lane: lane,
    action: action,
    hasEagerState: false,
    eagerState: null,
    next: null,
  };
  // ...
}

从源码中可以看到通过requestUpdateLane生成了一个 lane 的赛道模型,然后将这个赛道模型包装到一个 update 对象(对应的就是一个任务)里。看一下requestUpdateLane的源码

function requestUpdateLane(fiber) {
  // Special cases
  var mode = fiber.mode;

  // 非并发,产生同步优先级
  if ((mode & ConcurrentMode) === NoMode) {
    return SyncLane;

    // 判断是否在 render 阶段产生的调度
  } else if (
    (executionContext & RenderContext) !== NoContext &&
    workInProgressRootRenderLanes !== NoLanes
  ) {
    // This is a render phase update. These are not officially supported. The
    // old behavior is to give this the same "thread" (lanes) as
    // whatever is currently rendering. So if you call `setState` on a component
    // that happens later in the same render, it will flush. Ideally, we want to
    // remove the special case and treat them as if they came from an
    // interleaved event. Regardless, this pattern is not officially supported.
    // This behavior is only a fallback. The flag only exists until we can roll
    // out the setState warning, since existing code might accidentally rely on
    // the current behavior.
    return pickArbitraryLane(workInProgressRootRenderLanes);
  }

  // 判断是否为过渡优先级
  var isTransition = requestCurrentTransition() !== NoTransition;

  if (isTransition) {
    // updates at the same priority within the same event. To do this, the
    // inputs to the algorithm must be the same.
    //
    // The trick we use is to cache the first of each of these inputs within an
    // event. Then reset the cached values once we can be sure the event is
    // over. Our heuristic for that is whenever we enter a concurrent work loop.

    if (currentEventTransitionLane === NoLane) {
      // All transitions within the same event are assigned the same lane.
      currentEventTransitionLane = claimNextTransitionLane();
    }

    return currentEventTransitionLane;
  } // Updates originating inside certain React methods, like flushSync, have
  // their priority set by tracking it with a context variable.
  //
  // The opaque type returned by the host config is internally a lane, so we can
  // use that directly.
  // TODO: Move this type conversion to the event priority module.

  // 判断是否有手动设置优先级
  var updateLane = getCurrentUpdatePriority();

  if (updateLane !== NoLane) {
    return updateLane;
  } // This update originated outside React. Ask the host environment for an
  // appropriate priority, based on the type of event.
  //
  // The opaque type returned by the host config is internally a lane, so we can
  // use that directly.
  // TODO: Move this type conversion to the event priority module.

  // 找到事件优先级
  var eventLane = getCurrentEventPriority();
  return eventLane;
}
  1. 如果当前应用未开启并发模式,返回 SyncLane(同步更新)
  2. 是否为"render"阶段更新。
  3. 是否与transition相关,如果与 transition 相关,会计算优先级。
  4. 是否有"手动设置的优先级"
  5. 返回事件的优先级。

从上述代码中可以看到 React 会根据不同的情况返回不同的优先级,默认返回的是事件优先级。那相当于当我们在 click 事件中如果通过 setState 触发一个更新时,返回的就是事件优先级。

冒泡 Fiber

设置更新的节点是比较深的 FiberNode 节点,参与调度的节点是FiberRootNode,为了方便使用 lane,会向上冒泡,直到 FiberRootNode。完成这一功能的函数是markUpdateLaneFromFiberToRoot。

// 参与调度的是 FiberRootNode,产生 update 是某一个 fiberNode,需要将产生update 的 fiberNode 向上冒泡到 FiberRootNode,这样可以很方便的捕获和操作节点
function markUpdateLaneFromFiberToRoot(sourceFiber, lane) {
  // Update the source fiber's lanes
  /*KaSong*/ logHook(
    "changeLanes",
    "markUpdateLaneFromFiberToRoot",
    sourceFiber.lanes,
    mergeLanes(sourceFiber.lanes, lane)
  );
  sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
  var alternate = sourceFiber.alternate;

  if (alternate !== null) {
    alternate.lanes = mergeLanes(alternate.lanes, lane);
  }

  {
    if (
      alternate === null &&
      (sourceFiber.flags & (Placement | Hydrating)) !== NoFlags
    ) {
      warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
    }
  } // Walk the parent path to the root and update the child lanes.

  var node = sourceFiber;
  var parent = sourceFiber.return;

  // 每个祖先 fiberNode 都回附加 "源 fiberNode"选定的 lane
  while (parent !== null) {
    parent.childLanes = mergeLanes(parent.childLanes, lane);
    alternate = parent.alternate;

    if (alternate !== null) {
      alternate.childLanes = mergeLanes(alternate.childLanes, lane);
    } else {
      {
        if ((parent.flags & (Placement | Hydrating)) !== NoFlags) {
          warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
        }
      }
    }

    node = parent;
    parent = parent.return;
  }

  if (node.tag === HostRoot) {
    var root = node.stateNode;
    return root;
  } else {
    return null;
  }
}
  1. 在向上遍历的过程中,生成的 lane 会附加到每一级父fiberNode 的childLanes中(和completeWork类似,一个向上冒泡的过程)。
  2. 通过lane和childLanes,可以直接判断当前节点是否需要更新以及子孙节点是否需要更新,方便进行优化。

调度 FiberRootNode

通过getNextLanes处理 lanes 模型,返回适合的 lanes 优先级。

// 选择要调度的 lanes
function getNextLanes(root, wipLanes) {
  // Early bailout if there's no pending work left.
  var pendingLanes = root.pendingLanes;

  if (pendingLanes === NoLanes) {
    return NoLanes;
  }

  var nextLanes = NoLanes;
  var suspendedLanes = root.suspendedLanes;
  var pingedLanes = root.pingedLanes; // Do not work on any idle work until all the non-idle work has finished,
  // even if the work is suspended.

  // 获取非空闲的 lanes  pendingLanes & NonIdleLanes; 将空闲任务从pendingLanes中分离出去
  var nonIdlePendingLanes = pendingLanes & NonIdleLanes;

  if (nonIdlePendingLanes !== NoLanes) {
    // 除去挂起的任务
    var nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;

    if (nonIdleUnblockedLanes !== NoLanes) {
      nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
    } else {
      // 如果nonIdlePingedLanes为空,则从挂起的优先级中选择最高的优先级。  pingedLanes:由于请求成功,取消挂起的 update 对应的 lane。
      var nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;

      if (nonIdlePingedLanes !== NoLanes) {
        nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
      }
    }
  } else {
    // The only remaining work is Idle.
    // 从闲置的任务中继续上诉逻辑。
    var unblockedLanes = pendingLanes & ~suspendedLanes;

    if (unblockedLanes !== NoLanes) {
      nextLanes = getHighestPriorityLanes(unblockedLanes);
    } else {
      if (pingedLanes !== NoLanes) {
        nextLanes = getHighestPriorityLanes(pingedLanes);
      }
    }
  }

  if (nextLanes === NoLanes) {
    // This should only be reachable if we're suspended
    // TODO: Consider warning in this path if a fallback timer is not scheduled.
    return NoLanes;
  } // If we're already in the middle of a render, switching lanes will interrupt
  // it and we'll lose our progress. We should only do this if the new lanes are
  // higher priority.

  if (
    wipLanes !== NoLanes &&
    wipLanes !== nextLanes && // If we already suspended with a delay, then interrupting is fine. Don't
    // bother waiting until the root is complete.
    (wipLanes & suspendedLanes) === NoLanes
  ) {
    var nextLane = getHighestPriorityLane(nextLanes);
    var wipLane = getHighestPriorityLane(wipLanes);

    if (
      // Tests whether the next lane is equal or lower priority than the wip
      // one. This works because the bits decrease in priority as you go left.
      nextLane >= wipLane || // Default priority updates should not interrupt transition updates. The
      // only difference between default updates and transition updates is that
      // default updates do not support refresh transitions.
      (nextLane === DefaultLane && (wipLane & TransitionLanes) !== NoLanes)
    ) {
      // Keep working on the existing in-progress tree. Do not interrupt.
      return wipLanes;
    }
  }

  if ((nextLanes & InputContinuousLane) !== NoLanes) {
    // When updates are sync by default, we entangle continuous priority updates
    // and default updates, so they render in the same batch. The only reason
    // they use separate lanes is because continuous updates should interrupt
    // transitions, but default updates should not.
    nextLanes |= pendingLanes & DefaultLane;
  } // Check for entangled lanes and add them to the batch.
  //
  // A lane is said to be entangled with another when it's not allowed to render
  // in a batch that does not also include the other lane. Typically we do this
  // when multiple updates have the same source, and we only want to respond to
  // the most recent event from that source.
  //
  // Note that we apply entanglements *after* checking for partial work above.
  // This means that if a lane is entangled during an interleaved event while
  // it's already rendering, we won't interrupt it. This is intentional, since
  // entanglement is usually "best effort": we'll try our best to render the
  // lanes in the same batch, but it's not worth throwing out partially
  // completed work in order to do it.
  // TODO: Reconsider this. The counter-argument is that the partial work
  // represents an intermediate state, which we don't want to show to the user.
  // And by spending extra time finishing it, we're increasing the amount of
  // time it takes to show the final state, which is what they are actually
  // waiting for.
  //
  // For those exceptions where entanglement is semantically important, like
  // useMutableSource, we should ensure that there is no partial work at the
  // time we apply the entanglement.

  var entangledLanes = root.entangledLanes;

  if (entangledLanes !== NoLanes) {
    var entanglements = root.entanglements;
    var lanes = nextLanes & entangledLanes;

    while (lanes > 0) {
      var index = pickArbitraryLaneIndex(lanes);
      var lane = 1 << index;
      nextLanes |= entanglements[index];
      /*KaSong*/ logHook(
        "getNextLanes_entangledLanes",
        entanglements[index]
      );
      lanes &= ~lane;
    }
  }

  return nextLanes;
}
  1. 分离两类任务优先级,一类是闲置任务,一类是非闲置任务。
  2. 如果是非闲置任务
    1. 分离阻塞任务(一般是对应着Suspense产生的任务)
    2. 如果分离后的阻塞任务为空,从阻塞任务中选择最高优先级。
  1. 如果是闲置任务
    1. 分离阻塞任务(一般是对应着Suspense产生的任务)
    2. 如果分离后的阻塞任务为空,从阻塞任务中选择最高优先级
  1. 通过找到的 lane 根 wipLanes 进行比较,如果 wipLanes 比当前 lane 优先级高,直接用 wip 的,反之用 lane。
  2. 处理纠缠相关。

调度策略

function ensureRootIsScheduled(){
  // ... 
  var nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
  );
  var newCallbackPriority = getHighestPriorityLane(nextLanes);
  // 如果当前优先级和新的优先级一致,则复用当前任务,不需要进行调度
  if (
    existingCallbackPriority === newCallbackPriority && // Special case related to `act`. If the currently scheduled task is a
    // Scheduler task, rather than an `act` task, cancel it and re-scheduled
    // on the `act` queue.
    !(
      ReactCurrentActQueue$1.current !== null &&
      existingCallbackNode !== fakeActCallbackNode
    )
  ) {
    {
      // If we're going to re-use an existing task, it needs to exist.
      // Assume that discrete update microtasks are non-cancellable and null.
      // TODO: Temporary until we confirm this warning is not fired.
      if (
        existingCallbackNode == null &&
        existingCallbackPriority !== SyncLane
      ) {
        error(
          "Expected scheduled callback to exist. This error is likely caused by a bug in React. Please file an issue."
        );
      }
    } // The priority hasn't changed. We can reuse the existing task. Exit.

    /*KaSong*/ logHook("priorityNotChange", newCallbackPriority);
    return;
  }
  var newCallbackNode;

  if (newCallbackPriority === SyncLane) {
    // Special case: Sync React callbacks are scheduled on a special
    // internal queue
    if (root.tag === LegacyRoot) {
      if (ReactCurrentActQueue$1.isBatchingLegacy !== null) {
        ReactCurrentActQueue$1.didScheduleLegacyUpdate = true;
      }
      /*KaSong*/ logHook(
        "scheduleCallback",
        "legacySync",
        performSyncWorkOnRoot.name
      );
      scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
    } else {
      /*KaSong*/ logHook(
        "scheduleCallback",
        "sync",
        performSyncWorkOnRoot.name
      );
      scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    }

    {
      // Flush the queue in a microtask.
      if (ReactCurrentActQueue$1.current !== null) {
        // Inside `act`, use our internal `act` queue so that these get flushed
        // at the end of the current scope even when using the sync version
        // of `act`.
        ReactCurrentActQueue$1.current.push(flushSyncCallbacks);
      } else {
        /*KaSong*/ logHook(
          "scheduleCallback",
          "microtask",
          flushSyncCallbacks.name
        );
        scheduleMicrotask(flushSyncCallbacks);
      }
    }

    newCallbackNode = null;
  } else {
    var schedulerPriorityLevel;

    switch (lanesToEventPriority(nextLanes)) {
      case DiscreteEventPriority:
        schedulerPriorityLevel = ImmediatePriority;
        break;

      case ContinuousEventPriority:
        schedulerPriorityLevel = UserBlockingPriority;
        break;

      case DefaultEventPriority:
        schedulerPriorityLevel = NormalPriority;
        break;

      case IdleEventPriority:
        schedulerPriorityLevel = IdlePriority;
        break;

      default:
        schedulerPriorityLevel = NormalPriority;
        break;
    }
    /*KaSong*/ logHook(
      "scheduleCallback",
      "concurrent",
      performConcurrentWorkOnRoot.name,
      schedulerPriorityLevel
    );
    newCallbackNode = scheduleCallback$1(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root)
    );
  }
}
  1. 通过获取的 lane 和现有优先级比较:
    1. 与现有优先级一致,中断当前新任务,重用之前任务。
    2. 新任务大于现有优先级,取消现有任务执行,优先执行优先级高的任务。
  1. 判断是否是同步更新,如果是同步更新,直接scheduleMicrotask(flushSyncCallbacks) 进行调度。
  2. 如果不是同步更新:
    1. 将最高优先级的 lane 调整为 Schedule 使用的优先级,使用 Scheduler调度回调函数(调度performConcurrentWorkOnRoot),在这个函数里可以使用可中断、可恢复、时间分片的特性。

处理饥饿问题

因为存在高优先级的任务可以打断低优先级的任务,可能会导致低优先级的任务一直不能运行的问题。所以需要一套饥饿任务的机制进行处理。

function performConcurrentWorkOnRoot(){
  var shouldTimeSlice =
    !includesBlockingLane(root, lanes) &&
    !includesExpiredLane(root, lanes) &&
    !didTimeout;
  // var shouldTimeSlice = true;

  /*KaSong*/ logHook("shouldTimeSlice", shouldTimeSlice);

  var exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)
    : renderRootSync(root, lanes);
}

performConcurrentWorkOnRoot函数是开启并发渲染的入口,通过源码可以看到通过shouldTimeSlice来启动并发渲染或同步渲染。shouldTimeSlice就是解决饥饿问题最关键的地方。

可以看到shouldTimeSlice由三个函数决定,这三个任务也决定着是调用同步渲染还是并发渲染。

function includesBlockingLane(root, lanes) {
  var SyncDefaultLanes =
    InputContinuousHydrationLane |
    InputContinuousLane |
    DefaultHydrationLane |
    DefaultLane;
  return (lanes & SyncDefaultLanes) !== NoLanes;
}

这个函数主要就是判断当前 lane 是否是包含阻塞的 lane,如果包含,则启动同步渲染。

function includesExpiredLane(root, lanes) {
  // This is a separate check from includesBlockingLane because a lane can
  // expire after a render has already started.
  return (lanes & root.expiredLanes) !== NoLanes;
}

之前不是讲过markStarvedLanesAsExpired用来标记任务是否过期吗,如果过期,会在expiredLanes标记"过期的 lane"。

这里会判断 lane 是否过期,如果过期开启同步渲染,这样就不可以打断了。

didTimeout也可以控制是否采用同步渲染还是并发渲染。判断条件是计算出来的expirationTime是否小于当前时间,如果小于当前事件说明已经过期,直接同步执行即可。

总结

1. 事件优先级

React 将事件分为不同优先级,用于决定任务的执行顺序:

  • 离散事件优先级(DiscreteEventPriority):优先级最高,同步执行。
    • 例如:clickinputchangeblurfocus
    • 对应 Lane:SyncLane
  • 连续事件优先级(ContinuousEventPriority):优先级次高。
    • 例如:touchmovescrolldragenter
    • 对应 Lane:InputContinuousLane
  • 默认事件优先级(DefaultEventPriority):普通优先级。
    • 对应 Lane:DefaultLane
  • 空闲事件优先级(IdleEventPriority):优先级最低。
    • 对应 Lane:IdleLane

2. Lane 优先级

Lane 是 React 用于表示任务优先级的模型,共有 31 种 Lane,优先级从高到低:

  • SyncLane:同步优先级,最高优先级。
  • InputContinuousLane:输入持续优先级。
  • DefaultLane:默认优先级。
  • TransitionLanes:过渡优先级,用于并发模式。
  • RetryLanes:重试优先级。
  • IdleLane:空闲优先级,最低优先级。

Lane 的优先级决定了任务的调度顺序,React 会根据任务的类型和上下文选择合适的 Lane。


3. 调度机制

React 的调度机制基于 Scheduler,总共有 6 种调度优先级:

  • ImmediatePriority(99):最高优先级,同步执行。
  • UserBlockingPriority(98):用户阻塞优先级。
  • NormalPriority(97):普通优先级。
  • LowPriority(96):低优先级。
  • IdlePriority(95):空闲优先级。
  • NoPriority(90):无优先级。

React 通过 lanesToEventPriority 将 Lane 优先级转换为事件优先级,再通过 ensureRootIsScheduled 将事件优先级转换为 Scheduler 优先级,最终决定任务的执行顺序。


4. Lane 的初始化与冒泡

  • Lane 初始化:
    • 通过 requestUpdateLane 生成 Lane,优先级取决于当前上下文(如是否在并发模式、是否是过渡更新等)。
    • 默认情况下,返回事件优先级(如 click 事件会返回 SyncLane)。
  • Lane 冒泡:
    • 通过 markUpdateLaneFromFiberToRoot 将 Lane 从触发更新的 Fiber 节点冒泡到 FiberRoot 节点。
    • 在冒泡过程中,Lane 会被附加到每个父节点的 childLanes 中,方便后续优化。

5. 调度策略

  • 任务调度:
    • 通过 getNextLanes 选择要调度的 Lane。
    • 如果新任务的优先级与当前任务一致,则复用当前任务;否则,取消当前任务,优先执行高优先级任务。
  • 同步与并发渲染:
    • 如果是同步任务(如 SyncLane),直接通过 scheduleMicrotask 调度。
    • 如果是并发任务,将 Lane 转换为 Scheduler 优先级,通过 scheduleCallback 调度。

6. 饥饿问题

由于高优先级任务可能会打断低优先级任务,导致低优先级任务一直无法执行,React 通过以下机制解决饥饿问题:

  • includesBlockingLane:判断当前 Lane 是否包含阻塞任务,如果是,则同步执行。
  • includesExpiredLane:判断当前 Lane 是否过期,如果是,则同步执行。
  • didTimeout:判断任务是否超时,如果超时,则同步执行。

通过以上机制,React 确保低优先级任务不会一直被高优先级任务打断,最终得到执行。


  • React 通过 事件优先级 和 Lane 优先级 管理任务的调度顺序。
  • 调度机制 基于 Scheduler,将 Lane 优先级转换为 Scheduler 优先级,决定任务的执行方式(同步或并发)。
  • 饥饿问题 通过判断任务的阻塞状态、过期状态和超时状态,确保低优先级任务最终得到执行。

这套机制使得 React 能够高效地处理用户交互和渲染任务,同时保证应用的响应性和性能。