react源码中的协调与调度

本文详细探讨了React源码中的协调与调度机制,包括requestEventTime、requestUpdateLane、findUpdateLane等关键步骤,阐述了同步任务和异步任务的执行机制,以及shouldYield和performUnitOfWork等流程。通过对React reconciler和scheduler流程的分析,揭示了React如何高效地处理更新任务和确保渲染性能。
摘要由CSDN通过智能技术生成

requestEventTime

其实在React执行过程中,会有数不清的任务要去执行,但是他们会有一个优先级的判定,假如两个事件的优先级一样,那么React是怎么去判定他们两谁先执行呢?

// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
export function requestEventTime() {
   
  if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
   
    // We're inside React, so it's fine to read the actual time.
    // react事件正在执行
    // executionContext
    // RenderContext 正在计算
    // CommitContext 正在提交
    // export const NoContext = /*             */ 0b0000000;
    // const BatchedContext = /*               */ 0b0000001;
    // const EventContext = /*                 */ 0b0000010;
    // const DiscreteEventContext = /*         */ 0b0000100;
    // const LegacyUnbatchedContext = /*       */ 0b0001000;
    // const RenderContext = /*                */ 0b0010000;
    // const CommitContext = /*                */ 0b0100000;
    // export const RetryAfterError = /*       */ 0b1000000;
    return now();
  }
  // 没有在react事件执行 NoTimestamp === -1
  if (currentEventTime !== NoTimestamp) {
    
    // 浏览器事件正在执行,返回上次的 currentEventTime
    return currentEventTime;
  }
  // 重新计算currentEventTime,当执行被中断后
  currentEventTime = now();
  return currentEventTime;
}

  • RenderContextCommitContext表示正在计算更新和正在提交更新,返回now()
  • 如果是浏览器事件正在执行中,返回上一次的currentEventTime
  • 如果终止或者中断react任务执行的时候,则重新获取执行时间now()。
  • 获取的时间越小,则执行的优先级越高

now()并不是单纯的new Date(),而是判定两次更新任务的时间是否小于10ms,来决定是否复用上一次的更新时间Scheduler_now的。

export const now = initialTimeMs < 10000 ? Scheduler_now : () => Scheduler_now() - initialTimeMs;

其实各位猜想一下,对于10ms级别的任务间隙时间,几乎是可以忽略不计的,那么这里就可以视为同样的任务,不需要有很大的性能开销,有利于批量更新

requestUpdateLane

requestEventTime位每一个需要执行的任务打上了触发更新时间标签,那么任务的优先级还需要进一步的确立,requestUpdateLane就是用来获取每一个任务执行的优先级的。

// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
export function requestUpdateLane(fiber: Fiber): Lane {
   
  // Special cases
  const mode = fiber.mode;
  if ((mode & BlockingMode) === NoMode) {
   
    return (SyncLane: Lane);
  } else if ((mode & ConcurrentMode) === NoMode) {
   
    return getCurrentPriorityLevel() === ImmediateSchedulerPriority
      ? (SyncLane: Lane)
      : (SyncBatchedLane: Lane);
  } else if (
    !deferRenderPhaseUpdateToNextBatch &&
    (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" (expiration time) 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);
  }

  // The algorithm for assigning an update to a lane should be stable for all
  // updates at the same priority within the same event. To do this, the inputs
  // to the algorithm must be the same. For example, we use the `renderLanes`
  // to avoid choosing a lane that is already in the middle of rendering.
  //
  // However, the "included" lanes could be mutated in between updates in the
  // same event, like if you perform an update inside `flushSync`. Or any other
  // code path that might call `prepareFreshStack`.
  //
  // 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.
  //
  // We'll do the same for `currentEventPendingLanes` below.
  if (currentEventWipLanes === NoLanes) {
   
    currentEventWipLanes = workInProgressRootIncludedLanes;
  }

  const isTransition = requestCurrentTransition() !== NoTransition;
  if (isTransition) {
   
    if (currentEventPendingLanes !== NoLanes) {
   
      currentEventPendingLanes =
        mostRecentlyUpdatedRoot !== null
          ? mostRecentlyUpdatedRoot.pendingLanes
          : NoLanes;
    }
    return findTransitionLane(currentEventWipLanes, currentEventPendingLanes);
  }

  // TODO: Remove this dependency on the Scheduler priority.
  // To do that, we're replacing it with an update lane priority.

  // 获取执行任务的优先级,便于调度
  const schedulerPriority = getCurrentPriorityLevel();

  // The old behavior was using the priority level of the Scheduler.
  // This couples React to the Scheduler internals, so we're replacing it
  // with the currentUpdateLanePriority above. As an example of how this
  // could be problematic, if we're not inside `Scheduler.runWithPriority`,
  // then we'll get the priority of the current running Scheduler task,
  // which is probably not what we want.
  let lane;
  if (
    // TODO: Temporary. We're removing the concept of discrete updates.
    (executionContext & DiscreteEventContext) !== NoContext &&

    // 用户block的类型事件
    schedulerPriority === UserBlockingSchedulerPriority
  ) {
   
    // 通过findUpdateLane函数重新计算lane
    lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
  } else {
   
    // 根据优先级计算法则计算lane
    const schedulerLanePriority = schedulerPriorityToLanePriority(
      schedulerPriority,
    );

    if (decoupleUpdatePriorityFromScheduler) {
   
      // In the new strategy, we will track the current update lane priority
      // inside React and use that priority to select a lane for this update.
      // For now, we're just logging when they're different so we can assess.
      const currentUpdateLanePriority = getCurrentUpdateLanePriority();

      if (
        schedulerLanePriority !== currentUpdateLanePriority &&
        currentUpdateLanePriority !== NoLanePriority
      ) {
   
        if (__DEV__) {
   
          console.error(
            'Expected current scheduler lane priority %s to match current update lane priority %s',
            schedulerLanePriority,
            currentUpdateLanePriority,
          );
        }
      }
    }
    // 根据计算得到的 schedulerLanePriority,计算更新的优先级 lane
    lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
  }

  return lane;
}

  • 通过getCurrentPriorityLevel获得所有执行任务的调度优先级schedulerPriority
  • 通过findUpdateLane计算lane,作为更新中的优先级。

findUpdateLane

export function findUpdateLane(
  lanePriority: LanePriority,  wipLanes: Lanes,
): Lane {
   
  switch (lanePriority) {
   
    case NoLanePriority:
      break;
    case SyncLanePriority:
      return SyncLane;
    case SyncBatchedLanePriority:
      return SyncBatchedLane;
    case InputDiscreteLanePriority: {
   
      const lane = pickArbitraryLane(InputDiscreteLanes & ~wipLanes);
      if (lane === NoLane) {
   
        // Shift to the next priority level
        return findUpdateLane(InputContinuousLanePriority, wipLanes);
      }
      return lane;
    }
    case InputContinuousLanePriority: {
   
      const lane = pickArbitraryLane(InputContinuousLanes & ~wipLanes);
      if (lane === NoLane) {
   
        // Shift to the next priority level
        return findUpdateLane(DefaultLanePriority, wipLanes);
      }
      return lane;
    }
    case DefaultLanePriority: {
   
      let lane = pickArbitraryLane(DefaultLanes & ~wipLanes);
      if (lane === NoLane) {
   
        // If all the default lanes are already being worked on, look for a
        // lane in the transition range.
        lane = pickArbitraryLane(TransitionLanes & ~wipLanes);
        if (lane === NoLane) {
   
          // All the transition lanes are taken, too. This should be very
          // rare, but as a last r
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值