• react源码中的协调与调度


    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • RenderContextCommitContext表示正在计算更新和正在提交更新,返回now()
    • 如果是浏览器事件正在执行中,返回上一次的currentEventTime
    • 如果终止或者中断react任务执行的时候,则重新获取执行时间now()。
    • 获取的时间越小,则执行的优先级越高

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

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

    其实各位猜想一下,对于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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 通过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 resort, pick a default lane. This will have
              // the effect of interrupting the current work-in-progress render.
              lane = pickArbitraryLane(DefaultLanes);
            }
          }
          return lane;
        }
        case TransitionPriority: // Should be handled by findTransitionLane instead
        case RetryLanePriority: // Should be handled by findRetryLane instead
          break;
        case IdleLanePriority:
          let lane = pickArbitraryLane(IdleLanes & ~wipLanes);
          if (lane === NoLane) {
            lane = pickArbitraryLane(IdleLanes);
          }
          return lane;
        default:
          // The remaining priorities are not valid for updates
          break;
      }
      invariant(
        false,
        'Invalid update priority: %s. This is a bug in React.',
        lanePriority,
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    相关参考视频讲解:进入学习

    lanePriority: LanePriority

    export opaque type LanePriority =
      | 0
      | 1
      | 2
      | 3
      | 4
      | 5
      | 6
      | 7
      | 8
      | 9
      | 10
      | 11
      | 12
      | 13
      | 14
      | 15
      | 16
      | 17;
    export opaque type Lanes = number;
    export opaque type Lane = number;
    export opaque type LaneMap<T> = Array<T>;
    
    import {
      ImmediatePriority as ImmediateSchedulerPriority,
      UserBlockingPriority as UserBlockingSchedulerPriority,
      NormalPriority as NormalSchedulerPriority,
      LowPriority as LowSchedulerPriority,
      IdlePriority as IdleSchedulerPriority,
      NoPriority as NoSchedulerPriority,
    } from './SchedulerWithReactIntegration.new';
    
    // 同步任务
    export const SyncLanePriority: LanePriority = 15;
    export const SyncBatchedLanePriority: LanePriority = 14;
    
    // 用户事件
    const InputDiscreteHydrationLanePriority: LanePriority = 13;
    export const InputDiscreteLanePriority: LanePriority = 12;
    
    const InputContinuousHydrationLanePriority: LanePriority = 11;
    export const InputContinuousLanePriority: LanePriority = 10;
    
    const DefaultHydrationLanePriority: LanePriority = 9;
    export const DefaultLanePriority: LanePriority = 8;
    
    const TransitionHydrationPriority: LanePriority = 7;
    export const TransitionPriority: LanePriority = 6;
    
    const RetryLanePriority: LanePriority = 5;
    
    const SelectiveHydrationLanePriority: LanePriority = 4;
    
    const IdleHydrationLanePriority: LanePriority = 3;
    const IdleLanePriority: LanePriority = 2;
    
    const OffscreenLanePriority: LanePriority = 1;
    
    export const NoLanePriority: LanePriority = 0;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    createUpdate

    export function createUpdate(eventTime: number, lane: Lane): Update<*> {
      const update: Update<*> = {
        eventTime, // 更新时间
        lane, // 优先级
    
        tag: UpdateState,//更新
        payload: null,// 需要更新的内容
        callback: null, // 更新完后的回调
    
        next: null, // 指向下一个更新
      };
      return update;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    createUpdate函数入参为eventTimelane,输出一个update对象,而对象中的tag表示此对象要进行什么样的操作。

    export const UpdateState = 0;// 更新
    export const ReplaceState = 1;//替换
    export const ForceUpdate = 2;//强制更新
    export const CaptureUpdate = 3;//xx更新
    
    • 1
    • 2
    • 3
    • 4
    • createUpdate就是单纯的给每一个任务进行包装,作为一个个体推入到更新队列中。

    enqueueUpdate

    export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
      // 获取当前更新队列?为啥呢?因为无法保证react是不是还有正在更新或者没有更新完毕的任务
      const updateQueue = fiber.updateQueue;
      //  如果更新队列为空,则表示fiber还未渲染,直接退出
      if (updateQueue === null) {
        // Only occurs if the fiber has been unmounted.
        return;
      }
    
      const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
      const pending = sharedQueue.pending;
      if (pending === null) {
        // This is the first update. Create a circular list.
         // 还记得那个更新对象吗?update.next =>
         // 如果pedding位null,表示第一次渲染,那么他的指针为update本身
        update.next = update;
      } else {
        // 将update插入到更新队列循环当中
        update.next = pending.next;
        pending.next = update;
      }
      sharedQueue.pending = update;
    
      if (__DEV__) {
        if (
          currentlyProcessingQueue === sharedQueue &&
          !didWarnUpdateInsideUpdate
        ) {
          console.error(
            'An update (setState, replaceState, or forceUpdate) was scheduled ' +
              'from inside an update function. Update functions should be pure, ' +
              'with zero side-effects. Consider using componentDidUpdate or a ' +
              'callback.',
          );
          didWarnUpdateInsideUpdate = true;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 这一步就是把需要更新的对象,与fiber更新队列关联起来。

    总结

    React通过获取事件的优先级,处理具有同样优先级的事件,创建更新对象并与fiber的更新队列关联起来。到这一步updateContainer这个流程就走完了,也下面就是开始他的协调阶段了。

    协调与调度

    协调调度的流程大致如图所示:

    在这里插入图片描述

    reconciler流程

    Reactreconciler流程以scheduleUpdateOnFiber为入口,并在checkForNestedUpdates里面处理任务更新的嵌套层数,如果嵌套层数过大( >50 ),就会认为是无效更新,则会抛出异常。之后便根据markUpdateLaneFromFiberToRoot对当前的fiber树,自底向上的递归fiberlane,根据lane做二进制比较或者位运算处理。详情如下:

    • 如果当前执行任务的优先级为同步,则去判断有无正在执行的React任务。如果没有则执行ensureRootIsScheduled,进行调度处理。
    • 如果当前任务优先级是异步执行,则执行ensureRootIsScheduled进行调度处理。
    export function scheduleUpdateOnFiber(
      fiber: Fiber,  lane: Lane,  eventTime: number,
    ) {
      // 检查嵌套层数,避免是循环做无效操作
      checkForNestedUpdates();
      warnAboutRenderPhaseUpdatesInDEV(fiber);
    
      // 更新当前更新队列里面的任务优先级,自底而上更新child.fiberLanes
      const root = markUpdateLaneFromFiberToRoot(fiber, lane);
      if (root === null) {
        warnAboutUpdateOnUnmountedFiberInDEV(fiber);
        return null;
      }
    
      // Mark that the root has a pending update.
      // 标记root有更新的,执行它
      markRootUpdated(root, lane, eventTime);
    
      if (root === workInProgressRoot) {
        // Received an update to a tree that's in the middle of rendering. Mark
        // that there was an interleaved update work on this root. Unless the
        // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render
        // phase update. In that case, we don't treat render phase updates as if
        // they were interleaved, for backwards compat reasons.
        if (
          deferRenderPhaseUpdateToNextBatch ||
          (executionContext & RenderContext) === NoContext
        ) {
          workInProgressRootUpdatedLanes = mergeLanes(
            workInProgressRootUpdatedLanes,
            lane,
          );
        }
        if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
          // The root already suspended with a delay, which means this render
          // definitely won't finish. Since we have a new update, let's mark it as
          // suspended now, right before marking the incoming update. This has the
          // effect of interrupting the current render and switching to the update.
          // TODO: Make sure this doesn't override pings that happen while we've
          // already started rendering.
          markRootSuspended(root, workInProgressRootRenderLanes);
        }
      }
    
      // TODO: requestUpdateLanePriority also reads the priority. Pass the
      // priority as an argument to that function and this one.
      // 获取当前优先级层次
      const priorityLevel = getCurrentPriorityLevel();
    
      // 同步任务,采用同步更新的方式
      if (lane === SyncLane) {
        if (
          // Check if we're inside unbatchedUpdates
          (executionContext & LegacyUnbatchedContext) !== NoContext &&
          // Check if we're not already rendering
          (executionContext & (RenderContext | CommitContext)) === NoContext
        ) {
          // Register pending interactions on the root to avoid losing traced interaction data.
          // 同步而且没有react任务在执行,调用performSyncWorkOnRoot
          schedulePendingInteractions(root, lane);
    
          // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
          // root inside of batchedUpdates should be synchronous, but layout updates
          // should be deferred until the end of the batch.
    
    
    
          performSyncWorkOnRoot(root);
    
    
    
        } else {
          // 如果有正在执行的react任务,那么执行它ensureRootIsScheduled去复用当前正在执行的任务
          // 跟本次更新一起进行
          ensureRootIsScheduled(root, eventTime);
    
    
    
    
    
          schedulePendingInteractions(root, lane);
          if (executionContext === NoContext) {
            // Flush the synchronous work now, unless we're already working or inside
            // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
            // scheduleCallbackForFiber to preserve the ability to schedule a callback
            // without immediately flushing it. We only do this for user-initiated
            // updates, to preserve historical behavior of legacy mode.
            resetRenderTimer();
            flushSyncCallbackQueue();
          }
        }
    
      } else {
        // Schedule a discrete update but only if it's not Sync.
        // 如果此次是异步任务
        if (
          (executionContext & DiscreteEventContext) !== NoContext &&
          // Only updates at user-blocking priority or greater are considered
          // discrete, even inside a discrete event.
          (priorityLevel === UserBlockingSchedulerPriority ||
            priorityLevel === ImmediateSchedulerPriority)
        ) {
          // This is the result of a discrete event. Track the lowest priority
          // discrete update per root so we can flush them early, if needed.
          if (rootsWithPendingDiscreteUpdates === null) {
            rootsWithPendingDiscreteUpdates = new Set([root]);
          } else {
            rootsWithPendingDiscreteUpdates.add(root);
          }
        }
    
        // Schedule other updates after in case the callback is sync.
        // 可以中断更新,只要调用ensureRootIsScheduled => performConcurrentWorkOnRoot
        ensureRootIsScheduled(root, eventTime);
    
    
    
    
    
        schedulePendingInteractions(root, lane);
      }
    
      // We use this when assigning a lane for a transition inside
      // `requestUpdateLane`. We assume it's the same as the root being updated,
      // since in the common case of a single root app it probably is. If it's not
      // the same root, then it's not a huge deal, we just might batch more stuff
      // together more than necessary.
      mostRecentlyUpdatedRoot = root;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    同步任务类型执行机制

    当任务的类型为同步任务,并且当前的js主线程空闲,会通过 performSyncWorkOnRoot(root) 方法开始执行同步任务。

    performSyncWorkOnRoot 里面主要做了两件事:

    • renderRootSync 从根节点开始进行同步渲染任务
    • commitRoot 执行 commit 流程

    当前js线程中有正在执行的任务时候,就会触发ensureRootIsScheduled函数。 ensureRootIsScheduled里面主要是处理当前加入的更新任务的lane是否有变化:

    • 如果没有变化则表示跟当前的schedule一起执行。
    • 如果有则创建新的schedule
    • 调用performSyncWorkOnRoot执行同步任务。
    function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
      const existingCallbackNode = root.callbackNode;
    
      // Check if any lanes are being starved by other work. If so, mark them as
      // expired so we know to work on those next.
      markStarvedLanesAsExpired(root, currentTime);
    
      // Determine the next lanes to work on, and their priority.
      const nextLanes = getNextLanes(
        root,
        root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
      );
      // This returns the priority level computed during the `getNextLanes` call.
      const newCallbackPriority = returnNextLanesPriority();
    
      if (nextLanes === NoLanes) {
        // Special case: There's nothing to work on.
        if (existingCallbackNode !== null) {
          cancelCallback(existingCallbackNode);
          root.callbackNode = null;
          root.callbackPriority = NoLanePriority;
        }
        return;
      }
    
      // Check if there's an existing task. We may be able to reuse it.
      if (existingCallbackNode !== null) {
        const existingCallbackPriority = root.callbackPriority;
        if (existingCallbackPriority === newCallbackPriority) {
          // The priority hasn't changed. We can reuse the existing task. Exit.
          return;
        }
        // The priority changed. Cancel the existing callback. We'll schedule a new
        // one below.
        cancelCallback(existingCallbackNode);
      }
    
      // Schedule a new callback.
      let newCallbackNode;
      if (newCallbackPriority === SyncLanePriority) {
        // Special case: Sync React callbacks are scheduled on a special
        // internal queue
        // 同步任务调用performSyncWorkOnRoot
        newCallbackNode = scheduleSyncCallback(
          performSyncWorkOnRoot.bind(null, root),
        );
      } else if (newCallbackPriority === SyncBatchedLanePriority) {
        newCallbackNode = scheduleCallback(
          ImmediateSchedulerPriority,
          performSyncWorkOnRoot.bind(null, root),
        );
      } else {
        // 异步任务调用 performConcurrentWorkOnRoot
        const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
          newCallbackPriority,
        );
        newCallbackNode = scheduleCallback(
          schedulerPriorityLevel,
          performConcurrentWorkOnRoot.bind(null, root),
        );
      }
    
      root.callbackPriority = newCallbackPriority;
      root.callbackNode = newCallbackNode;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    所以任务类型为同步的时候,不管js线程空闲与否,都会走到performSyncWorkOnRoot,进而走renderRootSyncworkLoopSync流程,而在workLoopSync中,只要workInProgress fiber不为null,则会一直循环执行performUnitOfWork,而performUnitOfWork中会去执行beginWorkcompleteWork,也就是上一章里面说的beginWork流程去创建每一个fiber节点

    // packages/react-reconciler/src/ReactFiberWorkLoop.old.js
    
    function workLoopSync() {
      while (workInProgress !== null) {
        performUnitOfWork(workInProgress);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    异步任务类型执行机制

    异步任务则会去执行performConcurrentWorkOnRoot,进而去执行renderRootConcurrentworkLoopConcurrent,但是与同步任务不同的是异步任务是可以中断的,这个可中断的关键字就在于shouldYield,它本身返回值是一个false,为true则可以中断。

    // packages/react-reconciler/src/ReactFiberWorkLoop.old.js
    
    function workLoopConcurrent() {
      while (workInProgress !== null && !shouldYield()) {
        performUnitOfWork(workInProgress);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    每一次在执行performUnitOfWork之前都会关注一下shouldYield()返回值,也就是说的reconciler过程可中断的意思。

    shouldYield

    // packages\scheduler\src\SchedulerPostTask.js
    export function unstable_shouldYield() {
      return getCurrentTime() >= deadline;
    }
    
    • 1
    • 2
    • 3
    • 4

    getCurrentTimenew Date()deadline为浏览器处理每一帧结束时间戳,所以这里表示的是,在浏览器每一帧空闲的时候,才会去处理此任务,如果当前任务在浏览器执行的某一帧里面,则会中断当前任务,等待浏览器当前帧执行完毕,等到下一帧空闲的时候,才会去执行当前任务。

    所以不管在workLoopConcurrent还是workLoopSync中,都会根据当前的workInProgress fiber是否为null来进行循环调用performUnitOfWork。根据流程图以及上面说的这一些,可以看得出来从beginWorkcompleteUnitOfWork这个过程究竟干了什么。

    这三章将会讲解fiber树的reconcileChildren过程、completeWork过程、commitMutationEffectsinsertOrAppendPlacementNodeIntoContainer(DOM)过程。这里将详细解读v17版本的Reactdiff算法虚拟dom到真实dom的创建函数生命钩子的执行流程等。

    performUnitOfWork

    function performUnitOfWork(unitOfWork: Fiber): void {
      // The current, flushed, state of this fiber is the alternate. Ideally
      // nothing should rely on this, but relying on it here means that we don't
      // need an additional field on the work in progress.
      const current = unitOfWork.alternate;
      setCurrentDebugFiberInDEV(unitOfWork);
    
      let next;
      if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
        startProfilerTimer(unitOfWork);
        next = beginWork(current, unitOfWork, subtreeRenderLanes);
        stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
      } else {
        // beginWork
        next = beginWork(current, unitOfWork, subtreeRenderLanes);
      }
    
      resetCurrentDebugFiberInDEV();
      unitOfWork.memoizedProps = unitOfWork.pendingProps;
      if (next === null) {
        // If this doesn't spawn new work, complete the current work.
        // completeUnitOfWork
        completeUnitOfWork(unitOfWork);
      } else {
        workInProgress = next;
      }
    
      ReactCurrentOwner.current = null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    所以在performUnitOfWork里面,每一次执行beginWork,进行workIngProgress更新,当遍历完毕整棵fiber树之后便会执行completeUnitOfWork

    beginWork

    在这里插入图片描述
    在这里插入图片描述

    我们可以看到beginWork就是originBeginWork得实际执行。我们翻开beginWork的源码可以看到,它便是根据不同的workInProgress.tag执行不同组件类型的处理函数,这里就不去拆分的太细,只有有想法便会单独出一篇文章讲述这个的细节,但是最后都会去调用reconcileChildren

    completeUnitOfWork

    当遍历完毕执行beginWork,遍历完毕之后就会走completeUnitOfWork

    function completeUnitOfWork(unitOfWork: Fiber): void {
      // Attempt to complete the current unit of work, then move to the next
      // sibling. If there are no more siblings, return to the parent fiber.
      let completedWork = unitOfWork;
      do {
        // The current, flushed, state of this fiber is the alternate. Ideally
        // nothing should rely on this, but relying on it here means that we don't
        // need an additional field on the work in progress.
        const current = completedWork.alternate;
        const returnFiber = completedWork.return;
    
        // Check if the work completed or if something threw.
        if ((completedWork.flags & Incomplete) === NoFlags) {
          setCurrentDebugFiberInDEV(completedWork);
          let next;
          if (
            !enableProfilerTimer ||
            (completedWork.mode & ProfileMode) === NoMode
          ) {
            // 绑定事件,更新props,更新dom
            next = completeWork(current, completedWork, subtreeRenderLanes);
          } else {
            startProfilerTimer(completedWork);
            next = completeWork(current, completedWork, subtreeRenderLanes);
            // Update render duration assuming we didn't error.
            stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
          }
          resetCurrentDebugFiberInDEV();
    
          if (next !== null) {
            // Completing this fiber spawned new work. Work on that next.
            workInProgress = next;
            return;
          }
    
          resetChildLanes(completedWork);
    
          if (
            returnFiber !== null &&
            // Do not append effects to parents if a sibling failed to complete
            (returnFiber.flags & Incomplete) === NoFlags
          ) {
            // Append all the effects of the subtree and this fiber onto the effect
            // list of the parent. The completion order of the children affects the
            // side-effect order.
    
            // 把已收集到的副作用,合并到父级effect lists中
            if (returnFiber.firstEffect === null) {
              returnFiber.firstEffect = completedWork.firstEffect;
            }
            if (completedWork.lastEffect !== null) {
              if (returnFiber.lastEffect !== null) {
                returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
              }
              returnFiber.lastEffect = completedWork.lastEffect;
            }
    
            // If this fiber had side-effects, we append it AFTER the children's
            // side-effects. We can perform certain side-effects earlier if needed,
            // by doing multiple passes over the effect list. We don't want to
            // schedule our own side-effect on our own list because if end up
            // reusing children we'll schedule this effect onto itself since we're
            // at the end.
            const flags = completedWork.flags;
    
            // Skip both NoWork and PerformedWork tags when creating the effect
            // list. PerformedWork effect is read by React DevTools but shouldn't be
            // committed.
            // 跳过NoWork,PerformedWork在commit阶段用不到
    
            if (flags > PerformedWork) {
              if (returnFiber.lastEffect !== null) {
                returnFiber.lastEffect.nextEffect = completedWork;
              } else {
                returnFiber.firstEffect = completedWork;
              }
              returnFiber.lastEffect = completedWork;
            }
          }
        } else {
          // This fiber did not complete because something threw. Pop values off
          // the stack without entering the complete phase. If this is a boundary,
          // capture values if possible.
          const next = unwindWork(completedWork, subtreeRenderLanes);
    
          // Because this fiber did not complete, don't reset its expiration time.
    
          if (next !== null) {
            // If completing this work spawned new work, do that next. We'll come
            // back here again.
            // Since we're restarting, remove anything that is not a host effect
            // from the effect tag.
            next.flags &= HostEffectMask;
            workInProgress = next;
            return;
          }
    
          if (
            enableProfilerTimer &&
            (completedWork.mode & ProfileMode) !== NoMode
          ) {
            // Record the render duration for the fiber that errored.
            stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
    
            // Include the time spent working on failed children before continuing.
            let actualDuration = completedWork.actualDuration;
            let child = completedWork.child;
            while (child !== null) {
              actualDuration += child.actualDuration;
              child = child.sibling;
            }
            completedWork.actualDuration = actualDuration;
          }
    
          if (returnFiber !== null) {
            // Mark the parent fiber as incomplete and clear its effect list.
            returnFiber.firstEffect = returnFiber.lastEffect = null;
            returnFiber.flags |= Incomplete;
          }
        }
    
        // 兄弟层指针
        const siblingFiber = completedWork.sibling;
        if (siblingFiber !== null) {
          // If there is more work to do in this returnFiber, do that next.
          workInProgress = siblingFiber;
          return;
        }
        // Otherwise, return to the parent
        completedWork = returnFiber;
        // Update the next thing we're working on in case something throws.
        workInProgress = completedWork;
      } while (completedWork !== null);
    
      // We've reached the root.
      if (workInProgressRootExitStatus === RootIncomplete) {
        workInProgressRootExitStatus = RootCompleted;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139

    他的作用便是逐层收集fiber树上已经被打上的副作用标签flags,一直收集到root上面以便于在commit阶段进行dom增删改

    在这里插入图片描述

    scheduler流程

    在这里应该有很多人不明白,协调调度是什么意思,通俗来讲:

    • 协调就是协同合作
    • 调度就是执行命令

    所以在React中协调就是一个js线程中,需要安排很多模块去完成整个流程,例如:同步异步lane的处理,reconcileChildren处理fiber节点等,保证整个流程有条不紊的执行。调度表现为让空闲的js线程(帧层面)去执行其他任务,这个过程称之为调度,那么它到底是怎么去做的呢?
    我们回到处理异步任务那里,我们会发现performConcurrentWorkOnRoot这个函数外面包裹了一层scheduleCallback

    newCallbackNode = scheduleCallback(
       schedulerPriorityLevel,
       performConcurrentWorkOnRoot.bind(null, root),
    )
    
    • 1
    • 2
    • 3
    • 4
    export function scheduleCallback(
      reactPriorityLevel: ReactPriorityLevel,  callback: SchedulerCallback,  options: SchedulerCallbackOptions | void | null,
    ) {
      const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
      return Scheduler_scheduleCallback(priorityLevel, callback, options);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    我们几经周折找到了声明函数的地方

    // packages/scheduler/src/Scheduler.js
    function unstable_scheduleCallback(priorityLevel, callback, options) {
      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:
          timeout = IMMEDIATE_PRIORITY_TIMEOUT;
          break;
        case UserBlockingPriority:
          timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
          break;
        case IdlePriority:
          timeout = IDLE_PRIORITY_TIMEOUT;
          break;
        case LowPriority:
          timeout = LOW_PRIORITY_TIMEOUT;
          break;
        case NormalPriority:
        default:
          timeout = NORMAL_PRIORITY_TIMEOUT;
          break;
      }
    
      var expirationTime = startTime + timeout;
    
      var newTask = {
        id: taskIdCounter++,
        callback,
        priorityLevel,
        startTime,
        expirationTime,
        sortIndex: -1,
      };
      if (enableProfiling) {
        newTask.isQueued = false;
      }
    
      if (startTime > currentTime) {
        // This is a delayed task.
        newTask.sortIndex = startTime;
        push(timerQueue, newTask);
        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);
        }
      } else {
        newTask.sortIndex = expirationTime;
        push(taskQueue, newTask);
        if (enableProfiling) {
          markTaskStart(newTask, currentTime);
          newTask.isQueued = true;
        }
        // Schedule a host callback, if needed. If we're already performing work,
        // wait until the next time we yield.
        if (!isHostCallbackScheduled && !isPerformingWork) {
          isHostCallbackScheduled = true;
          requestHostCallback(flushWork);
        }
      }
    
      return newTask;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • starttime > currentTime的时候,表示任务超时,插入超时队列。
    • 任务没有超时,插入调度队列
    • 执行requestHostCallback调度任务。
      // 创建消息通道
      const channel = new MessageChannel();
      const port = channel.port2;
      channel.port1.onmessage = performWorkUntilDeadline;
    
      // 告知scheduler开始调度
      requestHostCallback = function(callback) {
        scheduledHostCallback = callback;
        if (!isMessageLoopRunning) {
          isMessageLoopRunning = true;
          port.postMessage(null);
        }
      };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    react通过 new MessageChannel() 创建了消息通道,当发现js线程空闲时,通过postMessage通知 scheduler开始调度。performWorkUntilDeadline函数功能为处理react调度开始时间更新到结束时间。
    这里我们要关注一下设备帧速率。

      forceFrameRate = function(fps) {
        if (fps < 0 || fps > 125) {
          // Using console['error'] to evade Babel and ESLint
          console['error'](
            'forceFrameRate takes a positive int between 0 and 125, ' +
              'forcing frame rates higher than 125 fps is not supported',
          );
          return;
        }
        if (fps > 0) {
          yieldInterval = Math.floor(1000 / fps);
        } else {
          // reset the framerate
          yieldInterval = 5;
        }
      };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    performWorkUntilDeadline

      const performWorkUntilDeadline = () => {
        if (scheduledHostCallback !== null) {
          const currentTime = getCurrentTime();
          // Yield after `yieldInterval` ms, regardless of where we are in the vsync
          // cycle. This means there's always time remaining at the beginning of
          // the message event.
          // 更新当前帧结束时间
          deadline = currentTime + yieldInterval;
          const hasTimeRemaining = true;
          try {
            const hasMoreWork = scheduledHostCallback(
              hasTimeRemaining,
              currentTime,
            );
            // 还有任务就继续执行
            if (!hasMoreWork) {
              isMessageLoopRunning = false;
              scheduledHostCallback = null;
            } else {
              // If there's more work, schedule the next message event at the end
              // of the preceding one.
              // 没有就postMessage
              port.postMessage(null);
            }
          } catch (error) {
            // If a scheduler task throws, exit the current browser task so the
            // error can be observed.
            port.postMessage(null);
            throw error;
          }
        } else {
          isMessageLoopRunning = false;
        }
        // Yielding to the browser will give it a chance to paint, so we can
        // reset this.
        needsPaint = false;
      };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    在这里插入图片描述

    总结

    本文讲了React在状态改变的时候,会根据当前任务优先级,等一些列操作去创建workInProgress fiber链表树,在协调阶段,会根据浏览器每一帧去做比较,假如浏览器每一帧执行时间戳高于当前时间,则表示当前帧没有空闲时间,当前任务则必须要等到下一个空闲帧才能去执行的可中断的策略。还有关于beginWork的遍历执行更新fiber的节点。那么到这里这一章就讲述完毕了,下一章讲一讲React的diff算法

  • 相关阅读:
    00、计算机视觉入门与调优简介
    Java电子病历编辑器项目源码 采用B/S(Browser/Server)架构
    怎么使用动态代理IP提升网络安全,动态代理IP有哪些好处呢?
    多款大模型向公众开放,百模大战再升级?
    【RocketMQ】主从同步实现原理
    R语言非线性方程数值分析生物降解、植物生长数据:多项式、渐近回归、米氏方程、逻辑曲线、Gompertz、Weibull曲线...
    TextAttack配置中遇到的问题(TAADpapers)
    《数字图像处理-OpenCV/Python》连载(26)绘制椭圆和椭圆弧
    android源码学习-Toast实现原理讲解
    UI设计师岗位的基本职责八篇(合集)
  • 原文地址:https://blog.csdn.net/weixin_59558923/article/details/128149496