text: /**
   * Copyright (c) Facebook, Inc. and its affiliates.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @flow
   */
  
  import type {Fiber} from './ReactFiber';
  import type {FiberRoot} from './ReactFiberRoot';
  import type {ExpirationTime} from './ReactFiberExpirationTime';
  import type {ReactPriorityLevel} from './SchedulerWithReactIntegration';
  import type {Interaction} from 'scheduler/src/Tracing';
  import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
  import type {SuspenseState} from './ReactFiberSuspenseComponent';
  import type {Effect as HookEffect} from './ReactFiberHooks';
  
  import {
    warnAboutDeprecatedLifecycles,
    deferPassiveEffectCleanupDuringUnmount,
    runAllPassiveEffectDestroysBeforeCreates,
    enableUserTimingAPI,
    enableSuspenseServerRenderer,
    replayFailedUnitOfWorkWithInvokeGuardedCallback,
    enableProfilerTimer,
    enableSchedulerTracing,
    warnAboutUnmockedScheduler,
    flushSuspenseFallbacksInTests,
    disableSchedulerTimeoutBasedOnReactExpirationTime,
    enableTrainModelFix,
  } from 'shared/ReactFeatureFlags';
  import ReactSharedInternals from 'shared/ReactSharedInternals';
  import invariant from 'shared/invariant';
  
  import {
    scheduleCallback,
    cancelCallback,
    getCurrentPriorityLevel,
    runWithPriority,
    shouldYield,
    requestPaint,
    now,
    NoPriority,
    ImmediatePriority,
    UserBlockingPriority,
    NormalPriority,
    LowPriority,
    IdlePriority,
    flushSyncCallbackQueue,
    scheduleSyncCallback,
  } from './SchedulerWithReactIntegration';
  
  // The scheduler is imported here *only* to detect whether it's been mocked
  import * as Scheduler from 'scheduler';
  
  import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
  
  import {
    prepareForCommit,
    resetAfterCommit,
    scheduleTimeout,
    cancelTimeout,
    noTimeout,
    warnsIfNotActing,
  } from './ReactFiberHostConfig';
  
  import {createWorkInProgress, assignFiberPropertiesInDEV} from './ReactFiber';
  import {
    isRootSuspendedAtTime,
    markRootSuspendedAtTime,
    markRootFinishedAtTime,
    markRootUpdatedAtTime,
    markRootExpiredAtTime,
  } from './ReactFiberRoot';
  import {
    NoMode,
    StrictMode,
    ProfileMode,
    BlockingMode,
    ConcurrentMode,
  } from './ReactTypeOfMode';
  import {
    HostRoot,
    ClassComponent,
    SuspenseComponent,
    SuspenseListComponent,
    FunctionComponent,
    ForwardRef,
    MemoComponent,
    SimpleMemoComponent,
    Block,
  } from 'shared/ReactWorkTags';
  import {
    NoEffect,
    PerformedWork,
    Placement,
    Update,
    PlacementAndUpdate,
    Deletion,
    Ref,
    ContentReset,
    Snapshot,
    Callback,
    Passive,
    Incomplete,
    HostEffectMask,
    Hydrating,
    HydratingAndUpdate,
  } from 'shared/ReactSideEffectTags';
  import {
    NoWork,
    Sync,
    Never,
    msToExpirationTime,
    expirationTimeToMs,
    computeInteractiveExpiration,
    computeAsyncExpiration,
    computeSuspenseExpiration,
    inferPriorityFromExpirationTime,
    LOW_PRIORITY_EXPIRATION,
    Batched,
    Idle,
  } from './ReactFiberExpirationTime';
  import {beginWork as originalBeginWork} from './ReactFiberBeginWork';
  import {completeWork} from './ReactFiberCompleteWork';
  import {unwindWork, unwindInterruptedWork} from './ReactFiberUnwindWork';
  import {
    throwException,
    createRootErrorUpdate,
    createClassErrorUpdate,
  } from './ReactFiberThrow';
  import {
    commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
    commitLifeCycles as commitLayoutEffectOnFiber,
    commitPassiveHookEffects,
    commitPlacement,
    commitWork,
    commitDeletion,
    commitDetachRef,
    commitAttachRef,
    commitResetTextContent,
  } from './ReactFiberCommitWork';
  import {enqueueUpdate} from './ReactUpdateQueue';
  import {resetContextDependencies} from './ReactFiberNewContext';
  import {resetHooksAfterThrow, ContextOnlyDispatcher} from './ReactFiberHooks';
  import {createCapturedValue} from './ReactCapturedValue';
  
  import {
    recordCommitTime,
    startProfilerTimer,
    stopProfilerTimerIfRunningAndRecordDelta,
  } from './ReactProfilerTimer';
  
  // DEV stuff
  import getComponentName from 'shared/getComponentName';
  import ReactStrictModeWarnings from './ReactStrictModeWarnings';
  import {
    isRendering as ReactCurrentDebugFiberIsRenderingInDEV,
    resetCurrentFiber as resetCurrentDebugFiberInDEV,
    setCurrentFiber as setCurrentDebugFiberInDEV,
    getStackByFiberInDevAndProd,
  } from './ReactCurrentFiber';
  import {
    recordEffect,
    recordScheduleUpdate,
    startWorkTimer,
    stopWorkTimer,
    stopFailedWorkTimer,
    startWorkLoopTimer,
    stopWorkLoopTimer,
    startCommitTimer,
    stopCommitTimer,
    startCommitSnapshotEffectsTimer,
    stopCommitSnapshotEffectsTimer,
    startCommitHostEffectsTimer,
    stopCommitHostEffectsTimer,
    startCommitLifeCyclesTimer,
    stopCommitLifeCyclesTimer,
  } from './ReactDebugFiberPerf';
  import {
    invokeGuardedCallback,
    hasCaughtError,
    clearCaughtError,
  } from 'shared/ReactErrorUtils';
  import {onCommitRoot} from './ReactFiberDevToolsHook';
  
  const ceil = Math.ceil;
  
  const {
    ReactCurrentDispatcher,
    ReactCurrentOwner,
    IsSomeRendererActing,
  } = ReactSharedInternals;
  
  type ExecutionContext = number;
  
  const NoContext = /*                    */ 0b000000;
  const BatchedContext = /*               */ 0b000001;
  const EventContext = /*                 */ 0b000010;
  const DiscreteEventContext = /*         */ 0b000100;
  const LegacyUnbatchedContext = /*       */ 0b001000;
  const RenderContext = /*                */ 0b010000;
  const CommitContext = /*                */ 0b100000;
  
  type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5;
  const RootIncomplete = 0;
  const RootFatalErrored = 1;
  const RootErrored = 2;
  const RootSuspended = 3;
  const RootSuspendedWithDelay = 4;
  const RootCompleted = 5;
  
  export type Thenable = {
    then(resolve: () => mixed, reject?: () => mixed): Thenable | void,
    // Special flag to opt out of tracing interactions across a Suspense boundary.
    __reactDoNotTraceInteractions?: boolean,
    ...
  };
  
  // Describes where we are in the React execution stack
  let executionContext: ExecutionContext = NoContext;
  // The root we're working on
  let workInProgressRoot: FiberRoot | null = null;
  // The fiber we're working on
  let workInProgress: Fiber | null = null;
  // The expiration time we're rendering
  let renderExpirationTime: ExpirationTime = NoWork;
  // Whether to root completed, errored, suspended, etc.
  let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;
  // A fatal error, if one is thrown
  let workInProgressRootFatalError: mixed = null;
  // Most recent event time among processed updates during this render.
  // This is conceptually a time stamp but expressed in terms of an ExpirationTime
  // because we deal mostly with expiration times in the hot path, so this avoids
  // the conversion happening in the hot path.
  let workInProgressRootLatestProcessedExpirationTime: ExpirationTime = Sync;
  let workInProgressRootLatestSuspenseTimeout: ExpirationTime = Sync;
  let workInProgressRootCanSuspendUsingConfig: null | SuspenseConfig = null;
  // The work left over by components that were visited during this render. Only
  // includes unprocessed updates, not work in bailed out children.
  let workInProgressRootNextUnprocessedUpdateTime: ExpirationTime = NoWork;
  
  // If we're pinged while rendering we don't always restart immediately.
  // This flag determines if it might be worthwhile to restart if an opportunity
  // happens latere.
  let workInProgressRootHasPendingPing: boolean = false;
  // The most recent time we committed a fallback. This lets us ensure a train
  // model where we don't commit new loading states in too quick succession.
  let globalMostRecentFallbackTime: number = 0;
  const FALLBACK_THROTTLE_MS: number = 500;
  
  let nextEffect: Fiber | null = null;
  let hasUncaughtError = false;
  let firstUncaughtError = null;
  let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
  
  let rootDoesHavePassiveEffects: boolean = false;
  let rootWithPendingPassiveEffects: FiberRoot | null = null;
  let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoPriority;
  let pendingPassiveEffectsExpirationTime: ExpirationTime = NoWork;
  let pendingPassiveHookEffectsMount: Array<HookEffect | Fiber> = [];
  let pendingPassiveHookEffectsUnmount: Array<HookEffect | Fiber> = [];
  
  let rootsWithPendingDiscreteUpdates: Map<
    FiberRoot,
    ExpirationTime,
  > | null = null;
  
  // Use these to prevent an infinite loop of nested updates
  const NESTED_UPDATE_LIMIT = 50;
  let nestedUpdateCount: number = 0;
  let rootWithNestedUpdates: FiberRoot | null = null;
  
  const NESTED_PASSIVE_UPDATE_LIMIT = 50;
  let nestedPassiveUpdateCount: number = 0;
  
  let interruptedBy: Fiber | null = null;
  
  // Marks the need to reschedule pending interactions at these expiration times
  // during the commit phase. This enables them to be traced across components
  // that spawn new work during render. E.g. hidden boundaries, suspended SSR
  // hydration or SuspenseList.
  let spawnedWorkDuringRender: null | Array<ExpirationTime> = null;
  
  // Expiration times are computed by adding to the current time (the start
  // time). However, if two updates are scheduled within the same event, we
  // should treat their start times as simultaneous, even if the actual clock
  // time has advanced between the first and second call.
  
  // In other words, because expiration times determine how updates are batched,
  // we want all updates of like priority that occur within the same event to
  // receive the same expiration time. Otherwise we get tearing.
  let currentEventTime: ExpirationTime = NoWork;
  
  export function requestCurrentTimeForUpdate() {
    if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
      // We're inside React, so it's fine to read the actual time.
      return msToExpirationTime(now());
    }
    // We're not inside React, so we may be in the middle of a browser event.
    if (currentEventTime !== NoWork) {
      // Use the same start time for all updates until we enter React again.
      return currentEventTime;
    }
    // This is the first update since React yielded. Compute a new start time.
    currentEventTime = msToExpirationTime(now());
    return currentEventTime;
  }
  
  export function getCurrentTime() {
    return msToExpirationTime(now());
  }
  
  export function computeExpirationForFiber(
    currentTime: ExpirationTime,
    fiber: Fiber,
    suspenseConfig: null | SuspenseConfig,
  ): ExpirationTime {
    const mode = fiber.mode;
    if ((mode & BlockingMode) === NoMode) {
      return Sync;
    }
  
    const priorityLevel = getCurrentPriorityLevel();
    if ((mode & ConcurrentMode) === NoMode) {
      return priorityLevel === ImmediatePriority ? Sync : Batched;
    }
  
    if ((executionContext & RenderContext) !== NoContext) {
      // Use whatever time we're already rendering
      // TODO: Should there be a way to opt out, like with `runWithPriority`?
      return renderExpirationTime;
    }
  
    let expirationTime;
    if (suspenseConfig !== null) {
      // Compute an expiration time based on the Suspense timeout.
      expirationTime = computeSuspenseExpiration(
        currentTime,
        suspenseConfig.timeoutMs | 0 || LOW_PRIORITY_EXPIRATION,
      );
    } else {
      // Compute an expiration time based on the Scheduler priority.
      switch (priorityLevel) {
        case ImmediatePriority:
          expirationTime = Sync;
          break;
        case UserBlockingPriority:
          // TODO: Rename this to computeUserBlockingExpiration
          expirationTime = computeInteractiveExpiration(currentTime);
          break;
        case NormalPriority:
        case LowPriority: // TODO: Handle LowPriority
          // TODO: Rename this to... something better.
          expirationTime = computeAsyncExpiration(currentTime);
          break;
        case IdlePriority:
          expirationTime = Idle;
          break;
        default:
          invariant(false, 'Expected a valid priority level');
      }
    }
  
    // If we're in the middle of rendering a tree, do not update at the same
    // expiration time that is already rendering.
    // TODO: We shouldn't have to do this if the update is on a different root.
    // Refactor computeExpirationForFiber + scheduleUpdate so we have access to
    // the root when we check for this condition.
    if (workInProgressRoot !== null && expirationTime === renderExpirationTime) {
      // This is a trick to move this update into a separate batch
      expirationTime -= 1;
    }
  
    return expirationTime;
  }
  
  /**
   * 判断任务是否为同步 调用同步任务入口
   */
  export function scheduleUpdateOnFiber(
    fiber: Fiber,
    expirationTime: ExpirationTime,
  ) {
    /**
     * fiber: 初始化渲染时为 rootFiber, 即 <div id="root"></div> 对应的 Fiber 对象
     * expirationTime: 任务过期时间 => 1073741823
     */
    /**
     * 判断是否是无限循环的 update 如果是就报错
     * 在 componentWillUpdate 或者 componentDidUpdate 生命周期函数中重复调用
     * setState 方法时, 可能会发生这种情况, React 限制了嵌套更新的数量以防止无限循环
     * 限制的嵌套更新数量为 50, 可通过 NESTED_UPDATE_LIMIT 全局变量获取
     */
    checkForNestedUpdates();
    // 开发环境下执行的代码 忽略
    warnAboutRenderPhaseUpdatesInDEV(fiber);
    // 遍历更新子节点的过期时间 返回 FiberRoot
    const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
    if (root === null) {
      // 开发环境下执行 忽略
      warnAboutUpdateOnUnmountedFiberInDEV(fiber);
      return;
    }
    // 判断是否有高优先级任务打断当前正在执行的任务
    // 内部判断条件不成立 内部代码没有得到执行
    checkForInterruption(fiber, expirationTime);
  
    // 报告调度更新, 测试环境执行, 忽略
    recordScheduleUpdate();
  
    // 获取当前调度任务的优先级 数值类型 从90开始 数值越大 优先级越高
    // 97 普通优先级任务
    const priorityLevel = getCurrentPriorityLevel();
    // 判断任务是否是同步任务 Sync的值为: 1073741823
    if (expirationTime === Sync) {
      if (
        // 检查是否处于非批量更新模式
        (executionContext & LegacyUnbatchedContext) !== NoContext &&
        // 检查是否没有处于正在进行渲染的任务
        (executionContext & (RenderContext | CommitContext)) === NoContext
      ) {
        // 在根上注册待处理的交互, 以避免丢失跟踪的交互数据
        // 初始渲染时内部条件判断不成立, 内部代码没有得到执行
        schedulePendingInteractions(root, expirationTime);
        // 同步任务入口点
        performSyncWorkOnRoot(root);
      } else {
        ensureRootIsScheduled(root);
        schedulePendingInteractions(root, expirationTime);
        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.
          flushSyncCallbackQueue();
        }
      }
    } else {
      ensureRootIsScheduled(root);
      schedulePendingInteractions(root, expirationTime);
    }
    // 初始渲染不执行
    if (
      (executionContext & DiscreteEventContext) !== NoContext &&
      // Only updates at user-blocking priority or greater are considered
      // discrete, even inside a discrete event.
      (priorityLevel === UserBlockingPriority ||
        priorityLevel === ImmediatePriority)
    ) {
      // 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 Map([[root, expirationTime]]);
      } else {
        const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
        if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {
          rootsWithPendingDiscreteUpdates.set(root, expirationTime);
        }
      }
    }
  }
  export const scheduleWork = scheduleUpdateOnFiber;
  
  // This is split into a separate function so we can mark a fiber with pending
  // work without treating it as a typical update that originates from an event;
  // e.g. retrying a Suspense boundary isn't an update, but it does schedule work
  // on a fiber.
  function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
    // Update the source fiber's expiration time
    if (fiber.expirationTime < expirationTime) {
      fiber.expirationTime = expirationTime;
    }
    let alternate = fiber.alternate;
    if (alternate !== null && alternate.expirationTime < expirationTime) {
      alternate.expirationTime = expirationTime;
    }
    // Walk the parent path to the root and update the child expiration time.
    let node = fiber.return;
    let root = null;
    if (node === null && fiber.tag === HostRoot) {
      root = fiber.stateNode;
    } else {
      while (node !== null) {
        alternate = node.alternate;
        if (node.childExpirationTime < expirationTime) {
          node.childExpirationTime = expirationTime;
          if (
            alternate !== null &&
            alternate.childExpirationTime < expirationTime
          ) {
            alternate.childExpirationTime = expirationTime;
          }
        } else if (
          alternate !== null &&
          alternate.childExpirationTime < expirationTime
        ) {
          alternate.childExpirationTime = expirationTime;
        }
        if (node.return === null && node.tag === HostRoot) {
          root = node.stateNode;
          break;
        }
        node = node.return;
      }
    }
    if (root !== null) {
      if (workInProgressRoot === root) {
        // Received an update to a tree that's in the middle of rendering. Mark
        // that's unprocessed work on this root.
        markUnprocessedUpdateTime(expirationTime);
  
        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: This happens to work when receiving an update during the render
          // phase, because of the trick inside computeExpirationForFiber to
          // subtract 1 from `renderExpirationTime` to move it into a
          // separate bucket. But we should probably model it with an exception,
          // using the same mechanism we use to force hydration of a subtree.
          // TODO: This does not account for low pri updates that were already
          // scheduled before the root started rendering. Need to track the next
          // pending expiration time (perhaps by backtracking the return path) and
          // then trigger a restart in the `renderDidSuspendDelayIfPossible` path.
          markRootSuspendedAtTime(root, renderExpirationTime);
        }
      }
      // Mark that the root has a pending update.
      markRootUpdatedAtTime(root, expirationTime);
    }
  
    return root;
  }
  
  function getNextRootExpirationTimeToWorkOn(root: FiberRoot): ExpirationTime {
    // Determines the next expiration time that the root should render, taking
    // into account levels that may be suspended, or levels that may have
    // received a ping.
  
    const lastExpiredTime = root.lastExpiredTime;
    if (lastExpiredTime !== NoWork) {
      return lastExpiredTime;
    }
  
    // "Pending" refers to any update that hasn't committed yet, including if it
    // suspended. The "suspended" range is therefore a subset.
    const firstPendingTime = root.firstPendingTime;
    if (!isRootSuspendedAtTime(root, firstPendingTime)) {
      // The highest priority pending time is not suspended. Let's work on that.
      return firstPendingTime;
    }
  
    // If the first pending time is suspended, check if there's a lower priority
    // pending level that we know about. Or check if we received a ping. Work
    // on whichever is higher priority.
    const lastPingedTime = root.lastPingedTime;
    const nextKnownPendingLevel = root.nextKnownPendingLevel;
    const nextLevel =
      lastPingedTime > nextKnownPendingLevel
        ? lastPingedTime
        : nextKnownPendingLevel;
    if (
      enableTrainModelFix &&
      nextLevel <= Idle &&
      firstPendingTime !== nextLevel
    ) {
      // Don't work on Idle/Never priority unless everything else is committed.
      return NoWork;
    }
    return nextLevel;
  }
  
  // Use this function to schedule a task for a root. There's only one task per
  // root; if a task was already scheduled, we'll check to make sure the
  // expiration time of the existing task is the same as the expiration time of
  // the next level that the root has work on. This function is called on every
  // update, and right before exiting a task.
  function ensureRootIsScheduled(root: FiberRoot) {
    const lastExpiredTime = root.lastExpiredTime;
    if (lastExpiredTime !== NoWork) {
      // Special case: Expired work should flush synchronously.
      root.callbackExpirationTime = Sync;
      root.callbackPriority = ImmediatePriority;
      root.callbackNode = scheduleSyncCallback(
        performSyncWorkOnRoot.bind(null, root),
      );
      return;
    }
  
    const expirationTime = getNextRootExpirationTimeToWorkOn(root);
    const existingCallbackNode = root.callbackNode;
    if (expirationTime === NoWork) {
      // There's nothing to work on.
      if (existingCallbackNode !== null) {
        root.callbackNode = null;
        root.callbackExpirationTime = NoWork;
        root.callbackPriority = NoPriority;
      }
      return;
    }
  
    // TODO: If this is an update, we already read the current time. Pass the
    // time as an argument.
    const currentTime = requestCurrentTimeForUpdate();
    const priorityLevel = inferPriorityFromExpirationTime(
      currentTime,
      expirationTime,
    );
  
    // If there's an existing render task, confirm it has the correct priority and
    // expiration time. Otherwise, we'll cancel it and schedule a new one.
    if (existingCallbackNode !== null) {
      const existingCallbackPriority = root.callbackPriority;
      const existingCallbackExpirationTime = root.callbackExpirationTime;
      if (
        // Callback must have the exact same expiration time.
        existingCallbackExpirationTime === expirationTime &&
        // Callback must have greater or equal priority.
        existingCallbackPriority >= priorityLevel
      ) {
        // Existing callback is sufficient.
        return;
      }
      // Need to schedule a new task.
      // TODO: Instead of scheduling a new task, we should be able to change the
      // priority of the existing one.
      cancelCallback(existingCallbackNode);
    }
  
    root.callbackExpirationTime = expirationTime;
    root.callbackPriority = priorityLevel;
  
    let callbackNode;
    if (expirationTime === Sync) {
      // Sync React callbacks are scheduled on a special internal queue
      callbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    } else if (disableSchedulerTimeoutBasedOnReactExpirationTime) {
      callbackNode = scheduleCallback(
        priorityLevel,
        performConcurrentWorkOnRoot.bind(null, root),
      );
    } else {
      callbackNode = scheduleCallback(
        priorityLevel,
        performConcurrentWorkOnRoot.bind(null, root),
        // Compute a task timeout based on the expiration time. This also affects
        // ordering because tasks are processed in timeout order.
        {timeout: expirationTimeToMs(expirationTime) - now()},
      );
    }
  
    root.callbackNode = callbackNode;
  }
  
  // This is the entry point for every concurrent task, i.e. anything that
  // goes through Scheduler.
  function performConcurrentWorkOnRoot(root, didTimeout) {
    // Since we know we're in a React event, we can clear the current
    // event time. The next update will compute a new event time.
    currentEventTime = NoWork;
  
    if (didTimeout) {
      // The render task took too long to complete. Mark the current time as
      // expired to synchronously render all expired work in a single batch.
      const currentTime = requestCurrentTimeForUpdate();
      markRootExpiredAtTime(root, currentTime);
      // This will schedule a synchronous callback.
      ensureRootIsScheduled(root);
      return null;
    }
  
    // Determine the next expiration time to work on, using the fields stored
    // on the root.
    const expirationTime = getNextRootExpirationTimeToWorkOn(root);
    if (expirationTime !== NoWork) {
      const originalCallbackNode = root.callbackNode;
      invariant(
        (executionContext & (RenderContext | CommitContext)) === NoContext,
        'Should not already be working.',
      );
  
      flushPassiveEffects();
  
      // If the root or expiration time have changed, throw out the existing stack
      // and prepare a fresh one. Otherwise we'll continue where we left off.
      if (
        root !== workInProgressRoot ||
        expirationTime !== renderExpirationTime
      ) {
        prepareFreshStack(root, expirationTime);
        startWorkOnPendingInteractions(root, expirationTime);
      }
  
      // If we have a work-in-progress fiber, it means there's still work to do
      // in this root.
      if (workInProgress !== null) {
        const prevExecutionContext = executionContext;
        executionContext |= RenderContext;
        const prevDispatcher = pushDispatcher(root);
        const prevInteractions = pushInteractions(root);
        startWorkLoopTimer(workInProgress);
        do {
          try {
            workLoopConcurrent();
            break;
          } catch (thrownValue) {
            handleError(root, thrownValue);
          }
        } while (true);
        resetContextDependencies();
        executionContext = prevExecutionContext;
        popDispatcher(prevDispatcher);
        if (enableSchedulerTracing) {
          popInteractions(((prevInteractions: any): Set<Interaction>));
        }
  
        if (workInProgressRootExitStatus === RootFatalErrored) {
          const fatalError = workInProgressRootFatalError;
          stopInterruptedWorkLoopTimer();
          prepareFreshStack(root, expirationTime);
          markRootSuspendedAtTime(root, expirationTime);
          ensureRootIsScheduled(root);
          throw fatalError;
        }
  
        if (workInProgress !== null) {
          // There's still work left over. Exit without committing.
          stopInterruptedWorkLoopTimer();
        } else {
          // We now have a consistent tree. The next step is either to commit it,
          // or, if something suspended, wait to commit it after a timeout.
          stopFinishedWorkLoopTimer();
  
          const finishedWork: Fiber = ((root.finishedWork =
            root.current.alternate): any);
          root.finishedExpirationTime = expirationTime;
          finishConcurrentRender(
            root,
            finishedWork,
            workInProgressRootExitStatus,
            expirationTime,
          );
        }
  
        ensureRootIsScheduled(root);
        if (root.callbackNode === originalCallbackNode) {
          // The task node scheduled for this root is the same one that's
          // currently executed. Need to return a continuation.
          return performConcurrentWorkOnRoot.bind(null, root);
        }
      }
    }
    return null;
  }
  
  function finishConcurrentRender(
    root,
    finishedWork,
    exitStatus,
    expirationTime,
  ) {
    // Set this to null to indicate there's no in-progress render.
    workInProgressRoot = null;
  
    switch (exitStatus) {
      case RootIncomplete:
      case RootFatalErrored: {
        invariant(false, 'Root did not complete. This is a bug in React.');
      }
      // Flow knows about invariant, so it complains if I add a break
      // statement, but eslint doesn't know about invariant, so it complains
      // if I do. eslint-disable-next-line no-fallthrough
      case RootErrored: {
        // If this was an async render, the error may have happened due to
        // a mutation in a concurrent event. Try rendering one more time,
        // synchronously, to see if the error goes away. If there are
        // lower priority updates, let's include those, too, in case they
        // fix the inconsistency. Render at Idle to include all updates.
        // If it was Idle or Never or some not-yet-invented time, render
        // at that time.
        markRootExpiredAtTime(
          root,
          expirationTime > Idle ? Idle : expirationTime,
        );
        // We assume that this second render pass will be synchronous
        // and therefore not hit this path again.
        break;
      }
      case RootSuspended: {
        markRootSuspendedAtTime(root, expirationTime);
        const lastSuspendedTime = root.lastSuspendedTime;
        if (expirationTime === lastSuspendedTime) {
          root.nextKnownPendingLevel = getRemainingExpirationTime(finishedWork);
        }
  
        // We have an acceptable loading state. We need to figure out if we
        // should immediately commit it or wait a bit.
  
        // If we have processed new updates during this render, we may now
        // have a new loading state ready. We want to ensure that we commit
        // that as soon as possible.
        const hasNotProcessedNewUpdates =
          workInProgressRootLatestProcessedExpirationTime === Sync;
        if (
          hasNotProcessedNewUpdates &&
          // do not delay if we're inside an act() scope
          !(
            __DEV__ &&
            flushSuspenseFallbacksInTests &&
            IsThisRendererActing.current
          )
        ) {
          // If we have not processed any new updates during this pass, then
          // this is either a retry of an existing fallback state or a
          // hidden tree. Hidden trees shouldn't be batched with other work
          // and after that's fixed it can only be a retry. We're going to
          // throttle committing retries so that we don't show too many
          // loading states too quickly.
          let msUntilTimeout =
            globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now();
          // Don't bother with a very short suspense time.
          if (msUntilTimeout > 10) {
            if (workInProgressRootHasPendingPing) {
              const lastPingedTime = root.lastPingedTime;
              if (lastPingedTime === NoWork || lastPingedTime >= expirationTime) {
                // This render was pinged but we didn't get to restart
                // earlier so try restarting now instead.
                root.lastPingedTime = expirationTime;
                prepareFreshStack(root, expirationTime);
                break;
              }
            }
  
            const nextTime = getNextRootExpirationTimeToWorkOn(root);
            if (nextTime !== NoWork && nextTime !== expirationTime) {
              // There's additional work on this root.
              break;
            }
            if (
              lastSuspendedTime !== NoWork &&
              lastSuspendedTime !== expirationTime
            ) {
              // We should prefer to render the fallback of at the last
              // suspended level. Ping the last suspended level to try
              // rendering it again.
              root.lastPingedTime = lastSuspendedTime;
              break;
            }
  
            // The render is suspended, it hasn't timed out, and there's no
            // lower priority work to do. Instead of committing the fallback
            // immediately, wait for more data to arrive.
            root.timeoutHandle = scheduleTimeout(
              commitRoot.bind(null, root),
              msUntilTimeout,
            );
            break;
          }
        }
        // The work expired. Commit immediately.
        commitRoot(root);
        break;
      }
      case RootSuspendedWithDelay: {
        markRootSuspendedAtTime(root, expirationTime);
        const lastSuspendedTime = root.lastSuspendedTime;
        if (expirationTime === lastSuspendedTime) {
          root.nextKnownPendingLevel = getRemainingExpirationTime(finishedWork);
        }
  
        if (
          // do not delay if we're inside an act() scope
          !(
            __DEV__ &&
            flushSuspenseFallbacksInTests &&
            IsThisRendererActing.current
          )
        ) {
          // We're suspended in a state that should be avoided. We'll try to
          // avoid committing it for as long as the timeouts let us.
          if (workInProgressRootHasPendingPing) {
            const lastPingedTime = root.lastPingedTime;
            if (lastPingedTime === NoWork || lastPingedTime >= expirationTime) {
              // This render was pinged but we didn't get to restart earlier
              // so try restarting now instead.
              root.lastPingedTime = expirationTime;
              prepareFreshStack(root, expirationTime);
              break;
            }
          }
  
          const nextTime = getNextRootExpirationTimeToWorkOn(root);
          if (nextTime !== NoWork && nextTime !== expirationTime) {
            // There's additional work on this root.
            break;
          }
          if (
            lastSuspendedTime !== NoWork &&
            lastSuspendedTime !== expirationTime
          ) {
            // We should prefer to render the fallback of at the last
            // suspended level. Ping the last suspended level to try
            // rendering it again.
            root.lastPingedTime = lastSuspendedTime;
            break;
          }
  
          let msUntilTimeout;
          if (workInProgressRootLatestSuspenseTimeout !== Sync) {
            // We have processed a suspense config whose expiration time we
            // can use as the timeout.
            msUntilTimeout =
              expirationTimeToMs(workInProgressRootLatestSuspenseTimeout) - now();
          } else if (workInProgressRootLatestProcessedExpirationTime === Sync) {
            // This should never normally happen because only new updates
            // cause delayed states, so we should have processed something.
            // However, this could also happen in an offscreen tree.
            msUntilTimeout = 0;
          } else {
            // If we don't have a suspense config, we're going to use a
            // heuristic to determine how long we can suspend.
            const eventTimeMs: number = inferTimeFromExpirationTime(
              workInProgressRootLatestProcessedExpirationTime,
            );
            const currentTimeMs = now();
            const timeUntilExpirationMs =
              expirationTimeToMs(expirationTime) - currentTimeMs;
            let timeElapsed = currentTimeMs - eventTimeMs;
            if (timeElapsed < 0) {
              // We get this wrong some time since we estimate the time.
              timeElapsed = 0;
            }
  
            msUntilTimeout = jnd(timeElapsed) - timeElapsed;
  
            // Clamp the timeout to the expiration time. TODO: Once the
            // event time is exact instead of inferred from expiration time
            // we don't need this.
            if (timeUntilExpirationMs < msUntilTimeout) {
              msUntilTimeout = timeUntilExpirationMs;
            }
          }
  
          // Don't bother with a very short suspense time.
          if (msUntilTimeout > 10) {
            // The render is suspended, it hasn't timed out, and there's no
            // lower priority work to do. Instead of committing the fallback
            // immediately, wait for more data to arrive.
            root.timeoutHandle = scheduleTimeout(
              commitRoot.bind(null, root),
              msUntilTimeout,
            );
            break;
          }
        }
        // The work expired. Commit immediately.
        commitRoot(root);
        break;
      }
      case RootCompleted: {
        // The work completed. Ready to commit.
        if (
          // do not delay if we're inside an act() scope
          !(
            __DEV__ &&
            flushSuspenseFallbacksInTests &&
            IsThisRendererActing.current
          ) &&
          workInProgressRootLatestProcessedExpirationTime !== Sync &&
          workInProgressRootCanSuspendUsingConfig !== null
        ) {
          // If we have exceeded the minimum loading delay, which probably
          // means we have shown a spinner already, we might have to suspend
          // a bit longer to ensure that the spinner is shown for
          // enough time.
          const msUntilTimeout = computeMsUntilSuspenseLoadingDelay(
            workInProgressRootLatestProcessedExpirationTime,
            expirationTime,
            workInProgressRootCanSuspendUsingConfig,
          );
          if (msUntilTimeout > 10) {
            markRootSuspendedAtTime(root, expirationTime);
            root.timeoutHandle = scheduleTimeout(
              commitRoot.bind(null, root),
              msUntilTimeout,
            );
            break;
          }
        }
        commitRoot(root);
        break;
      }
      default: {
        invariant(false, 'Unknown root exit status.');
      }
    }
  }
  
  // 进入 render 阶段, 构建 workInProgress Fiber 树
  function performSyncWorkOnRoot(root) {
    // 参数 root 为 fiberRoot 对象
  
    // 检查是否有过期的任务
    // 如果没有过期的任务 值为 0
    // 初始化渲染没有过期的任务待执行
    const lastExpiredTime = root.lastExpiredTime;
    // NoWork 值为 0
    // 如果有过期的任务 将过期时间设置为 lastExpiredTime 否则将过期时间设置为 Sync
    // 初始渲染过期时间被设置成了 Sync
    const expirationTime = lastExpiredTime !== NoWork ? lastExpiredTime : Sync;
  
    invariant(
      (executionContext & (RenderContext | CommitContext)) === NoContext,
      'Should not already be working.',
    );
  
    // 处理 useEffect
    flushPassiveEffects();
  
    // 如果 root 和 workInProgressRoot 不相等
    // 说明 workInProgressRoot 不存在, 说明还没有构建 workInProgress Fiber 树
    // workInProgressRoot 为全局变量 默认值为 null, 初始渲染时值为 null
    // expirationTime => 1073741823
    // renderExpirationTime => 0
    // true
    if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
      // 构建 workInProgressFiber 树及 rootFiber
      prepareFreshStack(root, expirationTime);
      // 初始渲染不执行 内部条件判断不成立
      startWorkOnPendingInteractions(root, expirationTime);
    }
    // workInProgress 如果不为 null
    if (workInProgress !== null) {
      const prevExecutionContext = executionContext;
      executionContext |= RenderContext;
      const prevDispatcher = pushDispatcher(root);
      const prevInteractions = pushInteractions(root);
      startWorkLoopTimer(workInProgress);
      do {
        try {
          // 以同步的方式开始构建 Fiber 对象
          workLoopSync();
          break;
        } catch (thrownValue) {
          handleError(root, thrownValue);
        }
      } while (true);
  
      resetContextDependencies();
      executionContext = prevExecutionContext;
      popDispatcher(prevDispatcher);
      if (enableSchedulerTracing) {
        popInteractions(((prevInteractions: any): Set<Interaction>));
      }
      // 初始渲染 不执行
      if (workInProgressRootExitStatus === RootFatalErrored) {
        const fatalError = workInProgressRootFatalError;
        stopInterruptedWorkLoopTimer();
        prepareFreshStack(root, expirationTime);
        markRootSuspendedAtTime(root, expirationTime);
        ensureRootIsScheduled(root);
        throw fatalError;
      }
  
      if (workInProgress !== null) {
        // 这是一个同步渲染, 所以我们应该完成整棵树
        // 无法提交不完整的 root, 此错误可能是由于React中的错误所致. 请提出问题.
        invariant(
          false,
          'Cannot commit an incomplete root. This error is likely caused by a ' +
            'bug in React. Please file an issue.',
        );
      } else {
        // We now have a consistent tree. Because this is a sync render, we
        // will commit it even if something suspended.
        stopFinishedWorkLoopTimer();
        // 将构建好的新 Fiber 对象存储在 finishedWork 属性中
        // 提交阶段使用
        root.finishedWork = (root.current.alternate: any);
        root.finishedExpirationTime = expirationTime;
        // 结束 render 阶段
        // 进入 commit 阶段
        finishSyncRender(root);
      }
  
      // Before exiting, make sure there's a callback scheduled for the next
      // pending level.
      ensureRootIsScheduled(root);
    }
  
    return null;
  }
  
  function finishSyncRender(root) {
    // 销毁 workInProgress Fiber 树
    // 因为待提交 Fiber 对象已经被存储在了 root.finishedWork 中
    workInProgressRoot = null;
    // 进入 commit 阶段
    commitRoot(root);
  }
  
  export function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
    markRootExpiredAtTime(root, expirationTime);
    ensureRootIsScheduled(root);
    if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
      flushSyncCallbackQueue();
    }
  }
  
  export function flushDiscreteUpdates() {
    // TODO: Should be able to flush inside batchedUpdates, but not inside `act`.
    // However, `act` uses `batchedUpdates`, so there's no way to distinguish
    // those two cases. Need to fix this before exposing flushDiscreteUpdates
    // as a public API.
    if (
      (executionContext & (BatchedContext | RenderContext | CommitContext)) !==
      NoContext
    ) {
      if (__DEV__) {
        if ((executionContext & RenderContext) !== NoContext) {
          console.error(
            'unstable_flushDiscreteUpdates: Cannot flush updates when React is ' +
              'already rendering.',
          );
        }
      }
      // We're already rendering, so we can't synchronously flush pending work.
      // This is probably a nested event dispatch triggered by a lifecycle/effect,
      // like `el.focus()`. Exit.
      return;
    }
    flushPendingDiscreteUpdates();
    // If the discrete updates scheduled passive effects, flush them now so that
    // they fire before the next serial event.
    flushPassiveEffects();
  }
  
  export function deferredUpdates<A>(fn: () => A): A {
    // TODO: Remove in favor of Scheduler.next
    return runWithPriority(NormalPriority, fn);
  }
  
  export function syncUpdates<A, B, C, R>(
    fn: (A, B, C) => R,
    a: A,
    b: B,
    c: C,
  ): R {
    return runWithPriority(ImmediatePriority, fn.bind(null, a, b, c));
  }
  
  function flushPendingDiscreteUpdates() {
    if (rootsWithPendingDiscreteUpdates !== null) {
      // For each root with pending discrete updates, schedule a callback to
      // immediately flush them.
      const roots = rootsWithPendingDiscreteUpdates;
      rootsWithPendingDiscreteUpdates = null;
      roots.forEach((expirationTime, root) => {
        markRootExpiredAtTime(root, expirationTime);
        ensureRootIsScheduled(root);
      });
      // Now flush the immediate queue.
      flushSyncCallbackQueue();
    }
  }
  
  export function batchedUpdates<A, R>(fn: (A) => R, a: A): R {
    const prevExecutionContext = executionContext;
    executionContext |= BatchedContext;
    try {
      return fn(a);
    } finally {
      executionContext = prevExecutionContext;
      if (executionContext === NoContext) {
        // Flush the immediate callbacks that were scheduled during this batch
        flushSyncCallbackQueue();
      }
    }
  }
  
  export function batchedEventUpdates<A, R>(fn: (A) => R, a: A): R {
    const prevExecutionContext = executionContext;
    executionContext |= EventContext;
    try {
      return fn(a);
    } finally {
      executionContext = prevExecutionContext;
      if (executionContext === NoContext) {
        // Flush the immediate callbacks that were scheduled during this batch
        flushSyncCallbackQueue();
      }
    }
  }
  
  export function discreteUpdates<A, B, C, D, R>(
    fn: (A, B, C) => R,
    a: A,
    b: B,
    c: C,
    d: D,
  ): R {
    const prevExecutionContext = executionContext;
    executionContext |= DiscreteEventContext;
    try {
      // Should this
      return runWithPriority(UserBlockingPriority, fn.bind(null, a, b, c, d));
    } finally {
      executionContext = prevExecutionContext;
      if (executionContext === NoContext) {
        // Flush the immediate callbacks that were scheduled during this batch
        flushSyncCallbackQueue();
      }
    }
  }
  
  export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
    const prevExecutionContext = executionContext;
    executionContext &= ~BatchedContext;
    executionContext |= LegacyUnbatchedContext;
    try {
      return fn(a);
    } finally {
      executionContext = prevExecutionContext;
      if (executionContext === NoContext) {
        // Flush the immediate callbacks that were scheduled during this batch
        flushSyncCallbackQueue();
      }
    }
  }
  
  export function flushSync<A, R>(fn: (A) => R, a: A): R {
    if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
      invariant(
        false,
        'flushSync was called from inside a lifecycle method. It cannot be ' +
          'called when React is already rendering.',
      );
    }
    const prevExecutionContext = executionContext;
    executionContext |= BatchedContext;
    try {
      return runWithPriority(ImmediatePriority, fn.bind(null, a));
    } finally {
      executionContext = prevExecutionContext;
      // Flush the immediate callbacks that were scheduled during this batch.
      // Note that this will happen even if batchedUpdates is higher up
      // the stack.
      flushSyncCallbackQueue();
    }
  }
  
  export function flushControlled(fn: () => mixed): void {
    const prevExecutionContext = executionContext;
    executionContext |= BatchedContext;
    try {
      runWithPriority(ImmediatePriority, fn);
    } finally {
      executionContext = prevExecutionContext;
      if (executionContext === NoContext) {
        // Flush the immediate callbacks that were scheduled during this batch
        flushSyncCallbackQueue();
      }
    }
  }
  
  /**
   * 构建 workInProgressFiber 树及 rootFiber
   */
  function prepareFreshStack(root, expirationTime) {
    // 为 FiberRoot 对象添加 finishedWork 属性
    // finishedWork 表示 render 阶段执行完成后构建的待提交的 Fiber 对象
    root.finishedWork = null;
    // 初始化 finishedExpirationTime 值为 0
    root.finishedExpirationTime = NoWork;
  
    const timeoutHandle = root.timeoutHandle;
    // 初始化渲染不执行 timeoutHandle => -1 noTimeout => -1
    if (timeoutHandle !== noTimeout) {
      // The root previous suspended and scheduled a timeout to commit a fallback
      // state. Now that we have additional work, cancel the timeout.
      root.timeoutHandle = noTimeout;
      // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
      cancelTimeout(timeoutHandle);
    }
  
    // 初始化渲染不执行 workInProgress 全局变量 初始化为 null
    // false
    if (workInProgress !== null) {
      let interruptedWork = workInProgress.return;
      while (interruptedWork !== null) {
        unwindInterruptedWork(interruptedWork);
        interruptedWork = interruptedWork.return;
      }
    }
    // 建构 workInProgress Fiber 树的 fiberRoot 对象
    workInProgressRoot = root;
    // 构建 workInProgress Fiber 树中的 rootFiber
    workInProgress = createWorkInProgress(root.current, null);
    renderExpirationTime = expirationTime;
    workInProgressRootExitStatus = RootIncomplete;
    workInProgressRootFatalError = null;
    workInProgressRootLatestProcessedExpirationTime = Sync;
    workInProgressRootLatestSuspenseTimeout = Sync;
    workInProgressRootCanSuspendUsingConfig = null;
    workInProgressRootNextUnprocessedUpdateTime = NoWork;
    workInProgressRootHasPendingPing = false;
    // true
    if (enableSchedulerTracing) {
      spawnedWorkDuringRender = null;
    }
  
    if (__DEV__) {
      ReactStrictModeWarnings.discardPendingWarnings();
    }
  }
  
  function handleError(root, thrownValue) {
    do {
      try {
        // Reset module-level state that was set during the render phase.
        resetContextDependencies();
        resetHooksAfterThrow();
        resetCurrentDebugFiberInDEV();
  
        if (workInProgress === null || workInProgress.return === null) {
          // Expected to be working on a non-root fiber. This is a fatal error
          // because there's no ancestor that can handle it; the root is
          // supposed to capture all errors that weren't caught by an error
          // boundary.
          workInProgressRootExitStatus = RootFatalErrored;
          workInProgressRootFatalError = thrownValue;
          // Set `workInProgress` to null. This represents advancing to the next
          // sibling, or the parent if there are no siblings. But since the root
          // has no siblings nor a parent, we set it to null. Usually this is
          // handled by `completeUnitOfWork` or `unwindWork`, but since we're
          // interntionally not calling those, we need set it here.
          // TODO: Consider calling `unwindWork` to pop the contexts.
          workInProgress = null;
          return null;
        }
  
        if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
          // Record the time spent rendering before an error was thrown. This
          // avoids inaccurate Profiler durations in the case of a
          // suspended render.
          stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
        }
  
        throwException(
          root,
          workInProgress.return,
          workInProgress,
          thrownValue,
          renderExpirationTime,
        );
        workInProgress = completeUnitOfWork(workInProgress);
      } catch (yetAnotherThrownValue) {
        // Something in the return path also threw.
        thrownValue = yetAnotherThrownValue;
        continue;
      }
      // Return to the normal work loop.
      return;
    } while (true);
  }
  
  function pushDispatcher(root) {
    const prevDispatcher = ReactCurrentDispatcher.current;
    ReactCurrentDispatcher.current = ContextOnlyDispatcher;
    if (prevDispatcher === null) {
      // The React isomorphic package does not include a default dispatcher.
      // Instead the first renderer will lazily attach one, in order to give
      // nicer error messages.
      return ContextOnlyDispatcher;
    } else {
      return prevDispatcher;
    }
  }
  
  function popDispatcher(prevDispatcher) {
    ReactCurrentDispatcher.current = prevDispatcher;
  }
  
  function pushInteractions(root) {
    if (enableSchedulerTracing) {
      const prevInteractions: Set<Interaction> | null = __interactionsRef.current;
      __interactionsRef.current = root.memoizedInteractions;
      return prevInteractions;
    }
    return null;
  }
  
  function popInteractions(prevInteractions) {
    if (enableSchedulerTracing) {
      __interactionsRef.current = prevInteractions;
    }
  }
  
  export function markCommitTimeOfFallback() {
    globalMostRecentFallbackTime = now();
  }
  
  export function markRenderEventTimeAndConfig(
    expirationTime: ExpirationTime,
    suspenseConfig: null | SuspenseConfig,
  ): void {
    if (
      expirationTime < workInProgressRootLatestProcessedExpirationTime &&
      expirationTime > Idle
    ) {
      workInProgressRootLatestProcessedExpirationTime = expirationTime;
    }
    if (suspenseConfig !== null) {
      if (
        expirationTime < workInProgressRootLatestSuspenseTimeout &&
        expirationTime > Idle
      ) {
        workInProgressRootLatestSuspenseTimeout = expirationTime;
        // Most of the time we only have one config and getting wrong is not bad.
        workInProgressRootCanSuspendUsingConfig = suspenseConfig;
      }
    }
  }
  
  export function markUnprocessedUpdateTime(
    expirationTime: ExpirationTime,
  ): void {
    if (expirationTime > workInProgressRootNextUnprocessedUpdateTime) {
      workInProgressRootNextUnprocessedUpdateTime = expirationTime;
    }
  }
  
  export function renderDidSuspend(): void {
    if (workInProgressRootExitStatus === RootIncomplete) {
      workInProgressRootExitStatus = RootSuspended;
    }
  }
  
  export function renderDidSuspendDelayIfPossible(): void {
    if (
      workInProgressRootExitStatus === RootIncomplete ||
      workInProgressRootExitStatus === RootSuspended
    ) {
      workInProgressRootExitStatus = RootSuspendedWithDelay;
    }
  
    // Check if there's a lower priority update somewhere else in the tree.
    if (
      workInProgressRootNextUnprocessedUpdateTime !== NoWork &&
      workInProgressRoot !== null
    ) {
      // Mark the current render as suspended, and then mark that there's a
      // pending update.
      // TODO: This should immediately interrupt the current render, instead
      // of waiting until the next time we yield.
      markRootSuspendedAtTime(workInProgressRoot, renderExpirationTime);
      markRootUpdatedAtTime(
        workInProgressRoot,
        workInProgressRootNextUnprocessedUpdateTime,
      );
    }
  }
  
  export function renderDidError() {
    if (workInProgressRootExitStatus !== RootCompleted) {
      workInProgressRootExitStatus = RootErrored;
    }
  }
  
  // Called during render to determine if anything has suspended.
  // Returns false if we're not sure.
  export function renderHasNotSuspendedYet(): boolean {
    // If something errored or completed, we can't really be sure,
    // so those are false.
    return workInProgressRootExitStatus === RootIncomplete;
  }
  
  function inferTimeFromExpirationTime(expirationTime: ExpirationTime): number {
    // We don't know exactly when the update was scheduled, but we can infer an
    // approximate start time from the expiration time.
    const earliestExpirationTimeMs = expirationTimeToMs(expirationTime);
    return earliestExpirationTimeMs - LOW_PRIORITY_EXPIRATION;
  }
  
  function inferTimeFromExpirationTimeWithSuspenseConfig(
    expirationTime: ExpirationTime,
    suspenseConfig: SuspenseConfig,
  ): number {
    // We don't know exactly when the update was scheduled, but we can infer an
    // approximate start time from the expiration time by subtracting the timeout
    // that was added to the event time.
    const earliestExpirationTimeMs = expirationTimeToMs(expirationTime);
    return (
      earliestExpirationTimeMs -
      (suspenseConfig.timeoutMs | 0 || LOW_PRIORITY_EXPIRATION)
    );
  }
  
  // The work loop is an extremely hot path. Tell Closure not to inline it.
  /** @noinline */
  function workLoopSync() {
    // workInProgress 是一个 fiber 对象
    // 它的值不为 null 意味着该 fiber 对象上仍然有更新要执行
    // while 方法支撑 render 阶段 所有 fiber 节点的构建
    while (workInProgress !== null) {
      workInProgress = performUnitOfWork(workInProgress);
    }
  }
  
  /** @noinline */
  function workLoopConcurrent() {
    // Perform work until Scheduler asks us to yield
    while (workInProgress !== null && !shouldYield()) {
      workInProgress = performUnitOfWork(workInProgress);
    }
  }
  // 构建 Fiber 对象
  function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
    // unitOfWork => workInProgress Fiber 树中的 rootFiber
    // current => currentFiber 树中的 rootFiber
    const current = unitOfWork.alternate;
    startWorkTimer(unitOfWork);
    // 开发环境执行 忽略
    setCurrentDebugFiberInDEV(unitOfWork);
    // 存储下一个要构建的子级 Fiber 对象
    let next;
    // 初始渲染 不执行
    // false
    if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
      startProfilerTimer(unitOfWork);
      next = beginWork(current, unitOfWork, renderExpirationTime);
      stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
    } else {
      // beginWork: 从父到子, 构建 Fiber 节点对象
      // 返回值 next 为当前节点的子节点
      next = beginWork(current, unitOfWork, renderExpirationTime);
    }
    // 开发环境执行 忽略
    resetCurrentDebugFiberInDEV();
    // 为旧的 props 属性赋值
    // 此次更新后 pendingProps 变为 memoizedProps
    unitOfWork.memoizedProps = unitOfWork.pendingProps;
    // 如果子节点不存在说明当前节点向下遍历子节点已经到底了
    // 继续向上返回 遇到兄弟节点 构建兄弟节点的子 Fiber 对象 直到返回到根 Fiber 对象
    if (next === null) {
      // 从子到父, 构建其余节点 Fiber 对象
      next = completeUnitOfWork(unitOfWork);
    }
  
    ReactCurrentOwner.current = null;
    return next;
  }
  
  /**
   * 1. 创建 Fiber 对象
   * 2. 创建每一个节点的真实 DOM 对象并将其添加到 stateNode 属性中
   * 3. 收集要执行 DOM 操作的 Fiber 节点, 组建 effect 链表结构
   */
  function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
    // 为 workInProgress 全局变量重新赋值
    workInProgress = unitOfWork;
    do {
      // 获取备份节点
      // 初始化渲染 非根 Fiber 对象没有备份节点 所以 current 为 null
      const current = workInProgress.alternate;
      // 父级 Fiber 对象, 非根 Fiber 对象都有父级
      const returnFiber = workInProgress.return;
      // 判断传入的 Fiber 对象是否构建完成, 任务调度相关
      // & 是表示位的与运算, 把左右两边的数字转化为二进制
      // 然后每一位分别进行比较, 如果相等就为1, 不相等即为0
      // 此处应用"位与"运算符的目的是"清零"
      // true
      if ((workInProgress.effectTag & Incomplete) === NoEffect) {
        // 开发环境代码 忽略
        setCurrentDebugFiberInDEV(workInProgress);
        let next;
        // 如果不能使用分析器的 timer, 直接执行 completeWork
        // enableProfilerTimer => true
        // 但此处无论条件是否成立都会执行 completeWork
        if (
          !enableProfilerTimer ||
          (workInProgress.mode & ProfileMode) === NoMode
        ) {
          // 重点代码(二)
          // 创建节点真实 DOM 对象并将其添加到 stateNode 属性中
          next = completeWork(current, workInProgress, renderExpirationTime);
        } else {
          // 否则执行分析器timer, 并执行 completeWork
          startProfilerTimer(workInProgress);
          // 创建节点真实 DOM 对象并将其添加到 stateNode 属性中
          next = completeWork(current, workInProgress, renderExpirationTime);
          // Update render duration assuming we didn't error.
          stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
        }
        stopWorkTimer(workInProgress);
        resetCurrentDebugFiberInDEV();
        resetChildExpirationTime(workInProgress);
        // 重点代码(一)
        // 如果子级存在
        if (next !== null) {
          // 返回子级 一直返回到 workLoopSync
          // 再重新执行 performUnitOfWork 构建子级 Fiber 节点对象
          return next;
        }
  
        // 构建 effect 链表结构
        // 如果不是根 Fiber 就是 true 否则就是 false
        // 将子树和此 Fiber 的所有 effect 附加到父级的 effect 列表中
        if (
          // 如果父 Fiber 存在 并且
          returnFiber !== null &&
          // 父 Fiber 对象中的 effectTag 为 0
          (returnFiber.effectTag & Incomplete) === NoEffect
        ) {
          // 将子树和此 Fiber 的所有副作用附加到父级的 effect 列表上
  
          // 以下两个判断的作用是搜集子 Fiber的 effect 到父 Fiber
          if (returnFiber.firstEffect === null) {
            // first
            returnFiber.firstEffect = workInProgress.firstEffect;
          }
  
          if (workInProgress.lastEffect !== null) {
            if (returnFiber.lastEffect !== null) {
              // next
              returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
            }
            // last
            returnFiber.lastEffect = workInProgress.lastEffect;
          }
  
          // 获取副作用标记
          // 初始渲染时除[根组件]以外的 Fiber, effectTag 值都为 0, 即不需要执行任何真实DOM操作
          // 根组件的 effectTag 值为 3, 即需要将此节点对应的真实DOM对象添加到页面中
          const effectTag = workInProgress.effectTag;
  
          // 创建 effect 列表时跳过 NoWork(0) 和 PerformedWork(1) 标记
          // PerformedWork 由 React DevTools 读取, 不提交
          // 初始渲染时 只有遍历到了根组件 判断条件才能成立, 将 effect 链表添加到 rootFiber
          // 初始渲染 FiberRoot 对象中的 firstEffect 和 lastEffect 都是 App 组件
          // 因为当所有节点在内存中构建完成后, 只需要一次将所有 DOM 添加到页面中
          if (effectTag > PerformedWork) {
            // false
            if (returnFiber.lastEffect !== null) {
              returnFiber.lastEffect.nextEffect = workInProgress;
            } else {
              // 为 fiberRoot 添加 firstEffect
              returnFiber.firstEffect = workInProgress;
            }
            // 为 fiberRoot 添加 lastEffect
            returnFiber.lastEffect = workInProgress;
          }
        }
      } 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(workInProgress, renderExpirationTime);
  
        // Because this fiber did not complete, don't reset its expiration time.
  
        if (
          enableProfilerTimer &&
          (workInProgress.mode & ProfileMode) !== NoMode
        ) {
          // Record the render duration for the fiber that errored.
          stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
  
          // Include the time spent working on failed children before continuing.
          let actualDuration = workInProgress.actualDuration;
          let child = workInProgress.child;
          while (child !== null) {
            actualDuration += child.actualDuration;
            child = child.sibling;
          }
          workInProgress.actualDuration = actualDuration;
        }
  
        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.
          // TODO: The name stopFailedWorkTimer is misleading because Suspense
          // also captures and restarts.
          stopFailedWorkTimer(workInProgress);
          next.effectTag &= HostEffectMask;
          return next;
        }
        stopWorkTimer(workInProgress);
  
        if (returnFiber !== null) {
          // Mark the parent fiber as incomplete and clear its effect list.
          returnFiber.firstEffect = returnFiber.lastEffect = null;
          returnFiber.effectTag |= Incomplete;
        }
      }
      // 获取下一个同级 Fiber 对象
      const siblingFiber = workInProgress.sibling;
      // 如果下一个同级 Fiber 对象存在
      if (siblingFiber !== null) {
        // 返回下一个同级 Fiber 对象
        return siblingFiber;
      }
      // 否则退回父级
      workInProgress = returnFiber;
    } while (workInProgress !== null);
  
    // 当执行到这里的时候, 说明遍历到了 root 节点, 已完成遍历
    // 更新 workInProgressRootExitStatus 的状态为 已完成
    if (workInProgressRootExitStatus === RootIncomplete) {
      workInProgressRootExitStatus = RootCompleted;
    }
    return null;
  }
  
  function getRemainingExpirationTime(fiber: Fiber) {
    const updateExpirationTime = fiber.expirationTime;
    const childExpirationTime = fiber.childExpirationTime;
    return updateExpirationTime > childExpirationTime
      ? updateExpirationTime
      : childExpirationTime;
  }
  
  function resetChildExpirationTime(completedWork: Fiber) {
    if (
      renderExpirationTime !== Never &&
      completedWork.childExpirationTime === Never
    ) {
      // The children of this component are hidden. Don't bubble their
      // expiration times.
      return;
    }
  
    let newChildExpirationTime = NoWork;
  
    // Bubble up the earliest expiration time.
    if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
      // In profiling mode, resetChildExpirationTime is also used to reset
      // profiler durations.
      let actualDuration = completedWork.actualDuration;
      let treeBaseDuration = completedWork.selfBaseDuration;
  
      // When a fiber is cloned, its actualDuration is reset to 0. This value will
      // only be updated if work is done on the fiber (i.e. it doesn't bailout).
      // When work is done, it should bubble to the parent's actualDuration. If
      // the fiber has not been cloned though, (meaning no work was done), then
      // this value will reflect the amount of time spent working on a previous
      // render. In that case it should not bubble. We determine whether it was
      // cloned by comparing the child pointer.
      const shouldBubbleActualDurations =
        completedWork.alternate === null ||
        completedWork.child !== completedWork.alternate.child;
  
      let child = completedWork.child;
      while (child !== null) {
        const childUpdateExpirationTime = child.expirationTime;
        const childChildExpirationTime = child.childExpirationTime;
        if (childUpdateExpirationTime > newChildExpirationTime) {
          newChildExpirationTime = childUpdateExpirationTime;
        }
        if (childChildExpirationTime > newChildExpirationTime) {
          newChildExpirationTime = childChildExpirationTime;
        }
        if (shouldBubbleActualDurations) {
          actualDuration += child.actualDuration;
        }
        treeBaseDuration += child.treeBaseDuration;
        child = child.sibling;
      }
      completedWork.actualDuration = actualDuration;
      completedWork.treeBaseDuration = treeBaseDuration;
    } else {
      let child = completedWork.child;
      while (child !== null) {
        const childUpdateExpirationTime = child.expirationTime;
        const childChildExpirationTime = child.childExpirationTime;
        if (childUpdateExpirationTime > newChildExpirationTime) {
          newChildExpirationTime = childUpdateExpirationTime;
        }
        if (childChildExpirationTime > newChildExpirationTime) {
          newChildExpirationTime = childChildExpirationTime;
        }
        child = child.sibling;
      }
    }
  
    completedWork.childExpirationTime = newChildExpirationTime;
  }
  
  function commitRoot(root) {
    // 获取任务优先级 97 => 普通优先级
    const renderPriorityLevel = getCurrentPriorityLevel();
    // 使用最高优先级执行当前任务, 因为 commit 阶段不可以被打断
    // ImmediatePriority, 优先级为 99, 最高优先级
    runWithPriority(
      ImmediatePriority,
      commitRootImpl.bind(null, root, renderPriorityLevel),
    );
    return null;
  }
  
  function commitRootImpl(root, renderPriorityLevel) {
    do {
      // 触发useEffect回调与其他同步任务
      // 由于这些任务可能触发新的渲染
      // 所以这里要一直遍历执行直到没有任务
      flushPassiveEffects();
    } while (rootWithPendingPassiveEffects !== null);
    // 开发环境执行 忽略
    flushRenderPhaseStrictModeWarningsInDEV();
  
    invariant(
      (executionContext & (RenderContext | CommitContext)) === NoContext,
      'Should not already be working.',
    );
    // 获取待提交 Fiber 对象 rootFiber
    const finishedWork = root.finishedWork;
  
    // 1073741823
    const expirationTime = root.finishedExpirationTime;
  
    // 如果没有任务要执行
    if (finishedWork === null) {
      // 阻止程序继续向下执行
      return null;
    }
    // 重置为默认值
    root.finishedWork = null;
    root.finishedExpirationTime = NoWork;
  
    invariant(
      finishedWork !== root.current,
      'Cannot commit the same tree as before. This error is likely caused by ' +
        'a bug in React. Please file an issue.',
    );
  
    // commitRoot 是最后阶段, 不会再被异步调用了
    // 所以清除 callback 相关的属性
    root.callbackNode = null;
    root.callbackExpirationTime = NoWork;
    root.callbackPriority = NoPriority;
    root.nextKnownPendingLevel = NoWork;
  
    startCommitTimer();
  
    // Update the first and last pending times on this root. The new first
    // pending time is whatever is left on the root fiber.
    const remainingExpirationTimeBeforeCommit = getRemainingExpirationTime(
      finishedWork,
    );
    // 重置优先级相关变量
    markRootFinishedAtTime(
      root,
      expirationTime,
      remainingExpirationTimeBeforeCommit,
    );
    // false
    if (root === workInProgressRoot) {
      // We can reset these now that they are finished.
      workInProgressRoot = null;
      workInProgress = null;
      renderExpirationTime = NoWork;
    } else {
      // 这表明我们处理的最后一个根与我们现在提交的根不同
      // 最常见的情况是在挂起的根超时时发生
    }
  
    // 将 effectList 赋值给 firstEffect
    // 由于每个 Fiber 的 effectList 只包含他的子孙节点
    // 所以根节点如果有 effectTag 则不会被包含进来
    // 所以这里将有 effectTag 的根节点插入到 effectList 尾部
    // 这样才能保证有 effect 的 fiber 都在 effectList 中
    let firstEffect;
    // finishedWork.effectTag => 0
    // PerformedWork => 1
    // false
    if (finishedWork.effectTag > PerformedWork) {
      if (finishedWork.lastEffect !== null) {
        finishedWork.lastEffect.nextEffect = finishedWork;
        firstEffect = finishedWork.firstEffect;
      } else {
        firstEffect = finishedWork;
      }
    } else {
      // 根节点没有 effectTag
      // 获取要执行 DOM 操作的副作用列表
      firstEffect = finishedWork.firstEffect;
    }
  
    // 以上代码为 commit 之前所做的准备工作
    // firstEffect 会在 commit 的三个子阶段会用到
  
    // true
    if (firstEffect !== null) {
      // 8
      const prevExecutionContext = executionContext;
      // 40
      executionContext |= CommitContext;
      const prevInteractions = pushInteractions(root);
  
      // Reset this to null before calling lifecycles
      ReactCurrentOwner.current = null;
  
      // The commit phase is broken into several sub-phases. We do a separate pass
      // of the effect list for each phase: all mutation effects come before all
      // layout effects, and so on.
  
      // The first phase a "before mutation" phase. We use this phase to read the
      // state of the host tree right before we mutate it. This is where
      // getSnapshotBeforeUpdate is called.
      startCommitSnapshotEffectsTimer();
      prepareForCommit(root.containerInfo);
      nextEffect = firstEffect;
      // commit 第一个子阶段
      // 处理类组件的 getSnapShotBeforeUpdate 生命周期函数
      do {
        if (__DEV__) {
          invokeGuardedCallback(null, commitBeforeMutationEffects, null);
          if (hasCaughtError()) {
            invariant(nextEffect !== null, 'Should be working on an effect.');
            const error = clearCaughtError();
            captureCommitPhaseError(nextEffect, error);
            nextEffect = nextEffect.nextEffect;
          }
        } else {
          try {
            commitBeforeMutationEffects();
          } catch (error) {
            invariant(nextEffect !== null, 'Should be working on an effect.');
            captureCommitPhaseError(nextEffect, error);
            nextEffect = nextEffect.nextEffect;
          }
        }
      } while (nextEffect !== null);
      stopCommitSnapshotEffectsTimer();
  
      if (enableProfilerTimer) {
        // Mark the current commit time to be shared by all Profilers in this
        // batch. This enables them to be grouped later.
        recordCommitTime();
      }
  
      // The next phase is the mutation phase, where we mutate the host tree.
      startCommitHostEffectsTimer();
      // commit 第二个子阶段
      nextEffect = firstEffect;
      do {
        if (__DEV__) {
          invokeGuardedCallback(
            null,
            commitMutationEffects,
            null,
            root,
            renderPriorityLevel,
          );
          if (hasCaughtError()) {
            invariant(nextEffect !== null, 'Should be working on an effect.');
            const error = clearCaughtError();
            captureCommitPhaseError(nextEffect, error);
            nextEffect = nextEffect.nextEffect;
          }
        } else {
          try {
            commitMutationEffects(root, renderPriorityLevel);
          } catch (error) {
            invariant(nextEffect !== null, 'Should be working on an effect.');
            captureCommitPhaseError(nextEffect, error);
            nextEffect = nextEffect.nextEffect;
          }
        }
      } while (nextEffect !== null);
      stopCommitHostEffectsTimer();
      resetAfterCommit(root.containerInfo);
  
      // The work-in-progress tree is now the current tree. This must come after
      // the mutation phase, so that the previous tree is still current during
      // componentWillUnmount, but before the layout phase, so that the finished
      // work is current during componentDidMount/Update.
      root.current = finishedWork;
  
      // The next phase is the layout phase, where we call effects that read
      // the host tree after it's been mutated. The idiomatic use case for this is
      // layout, but class component lifecycles also fire here for legacy reasons.
      startCommitLifeCyclesTimer();
      // commit 第三个子阶段
      nextEffect = firstEffect;
      do {
        if (__DEV__) {
          invokeGuardedCallback(
            null,
            commitLayoutEffects,
            null,
            root,
            expirationTime,
          );
          if (hasCaughtError()) {
            invariant(nextEffect !== null, 'Should be working on an effect.');
            const error = clearCaughtError();
            captureCommitPhaseError(nextEffect, error);
            nextEffect = nextEffect.nextEffect;
          }
        } else {
          try {
            commitLayoutEffects(root, expirationTime);
          } catch (error) {
            invariant(nextEffect !== null, 'Should be working on an effect.');
            captureCommitPhaseError(nextEffect, error);
            nextEffect = nextEffect.nextEffect;
          }
        }
      } while (nextEffect !== null);
      stopCommitLifeCyclesTimer();
  
      // 重置 nextEffect
      nextEffect = null;
  
      // Tell Scheduler to yield at the end of the frame, so the browser has an
      // opportunity to paint.
      requestPaint();
  
      if (enableSchedulerTracing) {
        popInteractions(((prevInteractions: any): Set<Interaction>));
      }
      executionContext = prevExecutionContext;
    } else {
      // No effects.
      root.current = finishedWork;
      // Measure these anyway so the flamegraph explicitly shows that there were
      // no effects.
      // TODO: Maybe there's a better way to report this.
      startCommitSnapshotEffectsTimer();
      stopCommitSnapshotEffectsTimer();
      if (enableProfilerTimer) {
        recordCommitTime();
      }
      startCommitHostEffectsTimer();
      stopCommitHostEffectsTimer();
      startCommitLifeCyclesTimer();
      stopCommitLifeCyclesTimer();
    }
  
    stopCommitTimer();
  
    const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
  
    if (rootDoesHavePassiveEffects) {
      // This commit has passive effects. Stash a reference to them. But don't
      // schedule a callback until after flushing layout work.
      rootDoesHavePassiveEffects = false;
      rootWithPendingPassiveEffects = root;
      pendingPassiveEffectsExpirationTime = expirationTime;
      pendingPassiveEffectsRenderPriority = renderPriorityLevel;
    } else {
      // We are done with the effect chain at this point so let's clear the
      // nextEffect pointers to assist with GC. If we have passive effects, we'll
      // clear this in flushPassiveEffects.
      nextEffect = firstEffect;
      while (nextEffect !== null) {
        const nextNextEffect = nextEffect.nextEffect;
        nextEffect.nextEffect = null;
        nextEffect = nextNextEffect;
      }
    }
  
    // Check if there's remaining work on this root
    const remainingExpirationTime = root.firstPendingTime;
    if (remainingExpirationTime !== NoWork) {
      if (enableSchedulerTracing) {
        if (spawnedWorkDuringRender !== null) {
          const expirationTimes = spawnedWorkDuringRender;
          spawnedWorkDuringRender = null;
          for (let i = 0; i < expirationTimes.length; i++) {
            scheduleInteractions(
              root,
              expirationTimes[i],
              root.memoizedInteractions,
            );
          }
        }
        schedulePendingInteractions(root, remainingExpirationTime);
      }
    } else {
      // If there's no remaining work, we can clear the set of already failed
      // error boundaries.
      legacyErrorBoundariesThatAlreadyFailed = null;
    }
  
    if (enableSchedulerTracing) {
      if (!rootDidHavePassiveEffects) {
        // If there are no passive effects, then we can complete the pending interactions.
        // Otherwise, we'll wait until after the passive effects are flushed.
        // Wait to do this until after remaining work has been scheduled,
        // so that we don't prematurely signal complete for interactions when there's e.g. hidden work.
        finishPendingInteractions(root, expirationTime);
      }
    }
  
    if (remainingExpirationTime === Sync) {
      // Count the number of times the root synchronously re-renders without
      // finishing. If there are too many, it indicates an infinite update loop.
      if (root === rootWithNestedUpdates) {
        nestedUpdateCount++;
      } else {
        nestedUpdateCount = 0;
        rootWithNestedUpdates = root;
      }
    } else {
      nestedUpdateCount = 0;
    }
  
    onCommitRoot(finishedWork.stateNode, expirationTime);
  
    // Always call this before exiting `commitRoot`, to ensure that any
    // additional work on this root is scheduled.
    ensureRootIsScheduled(root);
  
    if (hasUncaughtError) {
      hasUncaughtError = false;
      const error = firstUncaughtError;
      firstUncaughtError = null;
      throw error;
    }
  
    if ((executionContext & LegacyUnbatchedContext) !== NoContext) {
      // This is a legacy edge case. We just committed the initial mount of
      // a ReactDOM.render-ed root inside of batchedUpdates. The commit fired
      // synchronously, but layout updates should be deferred until the end
      // of the batch.
      return null;
    }
  
    // If layout work was scheduled, flush it now.
    flushSyncCallbackQueue();
    return null;
  }
  
  // commit 阶段的第一个子阶段
  // 调用类组件的 getSnapshotBeforeUpdate 生命周期函数
  function commitBeforeMutationEffects() {
    // 循环 effect 链
    while (nextEffect !== null) {
      // nextEffect 是 effect 链上从 firstEffect 到 lastEffect
      // 的每一个需要commit的 fiber 对象
  
      // 初始化渲染第一个 nextEffect 为 App 组件
      // effectTag => 3
      const effectTag = nextEffect.effectTag;
      // console.log(effectTag);
      // nextEffect = null;
      // return;
  
      // 如果 fiber 对象中里有 Snapshot 这个 effectTag 的话
      // Snapshot 和更新有关系 初始化渲染 不执行
      if ((effectTag & Snapshot) !== NoEffect) {
        // 开发环境执行 忽略
        setCurrentDebugFiberInDEV(nextEffect);
        // 计 effect 的数
        recordEffect();
        // 获取当前 fiber 节点
        const current = nextEffect.alternate;
        // 当 nextEffect 上有 Snapshot 这个 effectTag 时
        // 执行以下方法, 主要是类组件调用 getSnapshotBeforeUpdate 生命周期函数
        commitBeforeMutationEffectOnFiber(current, nextEffect);
        // 开发环境执行 忽略
        resetCurrentDebugFiberInDEV();
      }
      // 调度 useEffect
      // 初始化渲染 目前没有 不执行
      // false
      if ((effectTag & Passive) !== NoEffect) {
        // If there are passive effects, schedule a callback to flush at
        // the earliest opportunity.
        if (!rootDoesHavePassiveEffects) {
          rootDoesHavePassiveEffects = true;
          scheduleCallback(NormalPriority, () => {
            // 触发useEffect
            flushPassiveEffects();
            return null;
          });
        }
      }
      nextEffect = nextEffect.nextEffect;
    }
  }
  
  // commit 阶段的第二个子阶段
  // 根据 effectTag 执行 DOM 操作
  function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
    // 循环 effect 链
    while (nextEffect !== null) {
      // 开发环境执行 忽略
      setCurrentDebugFiberInDEV(nextEffect);
      // 获取 effectTag
      // 初始渲染第一次循环为 App 组件
      // 即将根组件及内部所有内容一次性添加到页面中
      const effectTag = nextEffect.effectTag;
  
      // 如果有文本节点, 将 value 置为''
      if (effectTag & ContentReset) {
        commitResetTextContent(nextEffect);
      }
      // 更新 ref
      if (effectTag & Ref) {
        const current = nextEffect.alternate;
        if (current !== null) {
          commitDetachRef(current);
        }
      }
  
      // 根据 effectTag 分别处理
      let primaryEffectTag =
        effectTag & (Placement | Update | Deletion | Hydrating);
      // 匹配 effectTag
      // 初始渲染 primaryEffectTag 为 2 匹配到 Placement
      switch (primaryEffectTag) {
        // 针对该节点及子节点进行插入操作
        case Placement: {
          commitPlacement(nextEffect);
          // effectTag 从 3 变为 1
          // 从 effect 标签中清除 "placement" 重置 effectTag 值
          // 以便我们知道在调用诸如componentDidMount之类的任何生命周期之前已将其插入。
          nextEffect.effectTag &= ~Placement;
          break;
        }
        // 插入并更新 DOM
        case PlacementAndUpdate: {
          // 插入
          commitPlacement(nextEffect);
          // Clear the "placement" from effect tag so that we know that this is
          // inserted, before any life-cycles like componentDidMount gets called.
          nextEffect.effectTag &= ~Placement;
  
          // 更新
          const current = nextEffect.alternate;
          commitWork(current, nextEffect);
          break;
        }
        // 服务器端渲染
        case Hydrating: {
          nextEffect.effectTag &= ~Hydrating;
          break;
        }
        // 服务器端渲染
        case HydratingAndUpdate: {
          nextEffect.effectTag &= ~Hydrating;
  
          // Update
          const current = nextEffect.alternate;
          commitWork(current, nextEffect);
          break;
        }
        // 更新 DOM
        case Update: {
          const current = nextEffect.alternate;
          commitWork(current, nextEffect);
          break;
        }
        // 删除 DOM
        case Deletion: {
          commitDeletion(root, nextEffect, renderPriorityLevel);
          break;
        }
      }
  
      // TODO: Only record a mutation effect if primaryEffectTag is non-zero.
      recordEffect();
  
      resetCurrentDebugFiberInDEV();
      nextEffect = nextEffect.nextEffect;
    }
  }
  
  // commit 阶段的第三个子阶段
  function commitLayoutEffects(
    root: FiberRoot,
    committedExpirationTime: ExpirationTime,
  ) {
    while (nextEffect !== null) {
      setCurrentDebugFiberInDEV(nextEffect);
      // 此时 effectTag 已经被重置为 1, 表示 DOM 操作已经完成
      const effectTag = nextEffect.effectTag;
      // 调用生命周期函数和钩子函数
      // 前提是类组件中调用了生命周期函数
      // 或者函数组件中调用了 useEffect
      if (effectTag & (Update | Callback)) {
        recordEffect();
        const current = nextEffect.alternate;
        // 类组件处理生命周期函数
        // 函数组件处理钩子函数
        commitLayoutEffectOnFiber(
          root,
          current,
          nextEffect,
          committedExpirationTime,
        );
      }
      // 赋值ref
      // false
      if (effectTag & Ref) {
        recordEffect();
        commitAttachRef(nextEffect);
      }
  
      resetCurrentDebugFiberInDEV();
      // 更新循环条件
      nextEffect = nextEffect.nextEffect;
    }
  }
  
  export function flushPassiveEffects() {
    if (pendingPassiveEffectsRenderPriority !== NoPriority) {
      const priorityLevel =
        pendingPassiveEffectsRenderPriority > NormalPriority
          ? NormalPriority
          : pendingPassiveEffectsRenderPriority;
      pendingPassiveEffectsRenderPriority = NoPriority;
      return runWithPriority(priorityLevel, flushPassiveEffectsImpl);
    }
  }
  
  export function enqueuePendingPassiveHookEffectMount(
    fiber: Fiber,
    effect: HookEffect,
  ): void {
    if (runAllPassiveEffectDestroysBeforeCreates) {
      pendingPassiveHookEffectsMount.push(effect, fiber);
      if (!rootDoesHavePassiveEffects) {
        rootDoesHavePassiveEffects = true;
        scheduleCallback(NormalPriority, () => {
          flushPassiveEffects();
          return null;
        });
      }
    }
  }
  
  export function enqueuePendingPassiveHookEffectUnmount(
    fiber: Fiber,
    effect: HookEffect,
  ): void {
    if (runAllPassiveEffectDestroysBeforeCreates) {
      pendingPassiveHookEffectsUnmount.push(effect, fiber);
      if (!rootDoesHavePassiveEffects) {
        rootDoesHavePassiveEffects = true;
        scheduleCallback(NormalPriority, () => {
          flushPassiveEffects();
          return null;
        });
      }
    }
  }
  
  function invokePassiveEffectCreate(effect: HookEffect): void {
    const create = effect.create;
    effect.destroy = create();
  }
  
  function flushPassiveEffectsImpl() {
    if (rootWithPendingPassiveEffects === null) {
      return false;
    }
    const root = rootWithPendingPassiveEffects;
    const expirationTime = pendingPassiveEffectsExpirationTime;
    rootWithPendingPassiveEffects = null;
    pendingPassiveEffectsExpirationTime = NoWork;
  
    invariant(
      (executionContext & (RenderContext | CommitContext)) === NoContext,
      'Cannot flush passive effects while already rendering.',
    );
    const prevExecutionContext = executionContext;
    executionContext |= CommitContext;
    const prevInteractions = pushInteractions(root);
  
    if (runAllPassiveEffectDestroysBeforeCreates) {
      // It's important that ALL pending passive effect destroy functions are called
      // before ANY passive effect create functions are called.
      // Otherwise effects in sibling components might interfere with each other.
      // e.g. a destroy function in one component may unintentionally override a ref
      // value set by a create function in another component.
      // Layout effects have the same constraint.
  
      // First pass: Destroy stale passive effects.
      let unmountEffects = pendingPassiveHookEffectsUnmount;
      pendingPassiveHookEffectsUnmount = [];
      for (let i = 0; i < unmountEffects.length; i += 2) {
        const effect = ((unmountEffects[i]: any): HookEffect);
        const fiber = ((unmountEffects[i + 1]: any): Fiber);
        const destroy = effect.destroy;
        effect.destroy = undefined;
        if (typeof destroy === 'function') {
          if (__DEV__) {
            setCurrentDebugFiberInDEV(fiber);
            invokeGuardedCallback(null, destroy, null);
            if (hasCaughtError()) {
              invariant(fiber !== null, 'Should be working on an effect.');
              const error = clearCaughtError();
              captureCommitPhaseError(fiber, error);
            }
            resetCurrentDebugFiberInDEV();
          } else {
            try {
              destroy();
            } catch (error) {
              invariant(fiber !== null, 'Should be working on an effect.');
              captureCommitPhaseError(fiber, error);
            }
          }
        }
      }
  
      // Second pass: Create new passive effects.
      let mountEffects = pendingPassiveHookEffectsMount;
      pendingPassiveHookEffectsMount = [];
      for (let i = 0; i < mountEffects.length; i += 2) {
        const effect = ((mountEffects[i]: any): HookEffect);
        const fiber = ((mountEffects[i + 1]: any): Fiber);
        if (__DEV__) {
          setCurrentDebugFiberInDEV(fiber);
          invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
          if (hasCaughtError()) {
            invariant(fiber !== null, 'Should be working on an effect.');
            const error = clearCaughtError();
            captureCommitPhaseError(fiber, error);
          }
          resetCurrentDebugFiberInDEV();
        } else {
          try {
            const create = effect.create;
            effect.destroy = create();
          } catch (error) {
            invariant(fiber !== null, 'Should be working on an effect.');
            captureCommitPhaseError(fiber, error);
          }
        }
      }
    } else {
      // Note: This currently assumes there are no passive effects on the root fiber
      // because the root is not part of its own effect list.
      // This could change in the future.
      let effect = root.current.firstEffect;
      while (effect !== null) {
        if (__DEV__) {
          setCurrentDebugFiberInDEV(effect);
          invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);
          if (hasCaughtError()) {
            invariant(effect !== null, 'Should be working on an effect.');
            const error = clearCaughtError();
            captureCommitPhaseError(effect, error);
          }
          resetCurrentDebugFiberInDEV();
        } else {
          try {
            commitPassiveHookEffects(effect);
          } catch (error) {
            invariant(effect !== null, 'Should be working on an effect.');
            captureCommitPhaseError(effect, error);
          }
        }
        const nextNextEffect = effect.nextEffect;
        // Remove nextEffect pointer to assist GC
        effect.nextEffect = null;
        effect = nextNextEffect;
      }
    }
  
    if (enableSchedulerTracing) {
      popInteractions(((prevInteractions: any): Set<Interaction>));
      finishPendingInteractions(root, expirationTime);
    }
  
    executionContext = prevExecutionContext;
  
    flushSyncCallbackQueue();
  
    // If additional passive effects were scheduled, increment a counter. If this
    // exceeds the limit, we'll fire a warning.
    nestedPassiveUpdateCount =
      rootWithPendingPassiveEffects === null ? 0 : nestedPassiveUpdateCount + 1;
  
    return true;
  }
  
  export function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
    return (
      legacyErrorBoundariesThatAlreadyFailed !== null &&
      legacyErrorBoundariesThatAlreadyFailed.has(instance)
    );
  }
  
  export function markLegacyErrorBoundaryAsFailed(instance: mixed) {
    if (legacyErrorBoundariesThatAlreadyFailed === null) {
      legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
    } else {
      legacyErrorBoundariesThatAlreadyFailed.add(instance);
    }
  }
  
  function prepareToThrowUncaughtError(error: mixed) {
    if (!hasUncaughtError) {
      hasUncaughtError = true;
      firstUncaughtError = error;
    }
  }
  export const onUncaughtError = prepareToThrowUncaughtError;
  
  function captureCommitPhaseErrorOnRoot(
    rootFiber: Fiber,
    sourceFiber: Fiber,
    error: mixed,
  ) {
    const errorInfo = createCapturedValue(error, sourceFiber);
    const update = createRootErrorUpdate(rootFiber, errorInfo, Sync);
    enqueueUpdate(rootFiber, update);
    const root = markUpdateTimeFromFiberToRoot(rootFiber, Sync);
    if (root !== null) {
      ensureRootIsScheduled(root);
      schedulePendingInteractions(root, Sync);
    }
  }
  
  export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
    if (sourceFiber.tag === HostRoot) {
      // Error was thrown at the root. There is no parent, so the root
      // itself should capture it.
      captureCommitPhaseErrorOnRoot(sourceFiber, sourceFiber, error);
      return;
    }
  
    let fiber = sourceFiber.return;
    while (fiber !== null) {
      if (fiber.tag === HostRoot) {
        captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error);
        return;
      } else if (fiber.tag === ClassComponent) {
        const ctor = fiber.type;
        const instance = fiber.stateNode;
        if (
          typeof ctor.getDerivedStateFromError === 'function' ||
          (typeof instance.componentDidCatch === 'function' &&
            !isAlreadyFailedLegacyErrorBoundary(instance))
        ) {
          const errorInfo = createCapturedValue(error, sourceFiber);
          const update = createClassErrorUpdate(
            fiber,
            errorInfo,
            // TODO: This is always sync
            Sync,
          );
          enqueueUpdate(fiber, update);
          const root = markUpdateTimeFromFiberToRoot(fiber, Sync);
          if (root !== null) {
            ensureRootIsScheduled(root);
            schedulePendingInteractions(root, Sync);
          }
          return;
        }
      }
      fiber = fiber.return;
    }
  }
  
  export function pingSuspendedRoot(
    root: FiberRoot,
    thenable: Thenable,
    suspendedTime: ExpirationTime,
  ) {
    const pingCache = root.pingCache;
    if (pingCache !== null) {
      // The thenable resolved, so we no longer need to memoize, because it will
      // never be thrown again.
      pingCache.delete(thenable);
    }
  
    if (workInProgressRoot === root && renderExpirationTime === suspendedTime) {
      // Received a ping at the same priority level at which we're currently
      // rendering. We might want to restart this render. This should mirror
      // the logic of whether or not a root suspends once it completes.
  
      // TODO: If we're rendering sync either due to Sync, Batched or expired,
      // we should probably never restart.
  
      // If we're suspended with delay, we'll always suspend so we can always
      // restart. If we're suspended without any updates, it might be a retry.
      // If it's early in the retry we can restart. We can't know for sure
      // whether we'll eventually process an update during this render pass,
      // but it's somewhat unlikely that we get to a ping before that, since
      // getting to the root most update is usually very fast.
      if (
        workInProgressRootExitStatus === RootSuspendedWithDelay ||
        (workInProgressRootExitStatus === RootSuspended &&
          workInProgressRootLatestProcessedExpirationTime === Sync &&
          now() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS)
      ) {
        // Restart from the root. Don't need to schedule a ping because
        // we're already working on this tree.
        prepareFreshStack(root, renderExpirationTime);
      } else {
        // Even though we can't restart right now, we might get an
        // opportunity later. So we mark this render as having a ping.
        workInProgressRootHasPendingPing = true;
      }
      return;
    }
  
    if (!isRootSuspendedAtTime(root, suspendedTime)) {
      // The root is no longer suspended at this time.
      return;
    }
  
    const lastPingedTime = root.lastPingedTime;
    if (lastPingedTime !== NoWork && lastPingedTime < suspendedTime) {
      // There's already a lower priority ping scheduled.
      return;
    }
  
    // Mark the time at which this ping was scheduled.
    root.lastPingedTime = suspendedTime;
  
    if (!enableTrainModelFix && root.finishedExpirationTime === suspendedTime) {
      // If there's a pending fallback waiting to commit, throw it away.
      root.finishedExpirationTime = NoWork;
      root.finishedWork = null;
    }
  
    ensureRootIsScheduled(root);
    schedulePendingInteractions(root, suspendedTime);
  }
  
  function retryTimedOutBoundary(
    boundaryFiber: Fiber,
    retryTime: ExpirationTime,
  ) {
    // The boundary fiber (a Suspense component or SuspenseList component)
    // previously was rendered in its fallback state. One of the promises that
    // suspended it has resolved, which means at least part of the tree was
    // likely unblocked. Try rendering again, at a new expiration time.
    if (retryTime === NoWork) {
      const suspenseConfig = null; // Retries don't carry over the already committed update.
      const currentTime = requestCurrentTimeForUpdate();
      retryTime = computeExpirationForFiber(
        currentTime,
        boundaryFiber,
        suspenseConfig,
      );
    }
    // TODO: Special case idle priority?
    const root = markUpdateTimeFromFiberToRoot(boundaryFiber, retryTime);
    if (root !== null) {
      ensureRootIsScheduled(root);
      schedulePendingInteractions(root, retryTime);
    }
  }
  
  export function retryDehydratedSuspenseBoundary(boundaryFiber: Fiber) {
    const suspenseState: null | SuspenseState = boundaryFiber.memoizedState;
    let retryTime = NoWork;
    if (suspenseState !== null) {
      retryTime = suspenseState.retryTime;
    }
    retryTimedOutBoundary(boundaryFiber, retryTime);
  }
  
  export function resolveRetryThenable(boundaryFiber: Fiber, thenable: Thenable) {
    let retryTime = NoWork; // Default
    let retryCache: WeakSet<Thenable> | Set<Thenable> | null;
    if (enableSuspenseServerRenderer) {
      switch (boundaryFiber.tag) {
        case SuspenseComponent:
          retryCache = boundaryFiber.stateNode;
          const suspenseState: null | SuspenseState = boundaryFiber.memoizedState;
          if (suspenseState !== null) {
            retryTime = suspenseState.retryTime;
          }
          break;
        case SuspenseListComponent:
          retryCache = boundaryFiber.stateNode;
          break;
        default:
          invariant(
            false,
            'Pinged unknown suspense boundary type. ' +
              'This is probably a bug in React.',
          );
      }
    } else {
      retryCache = boundaryFiber.stateNode;
    }
  
    if (retryCache !== null) {
      // The thenable resolved, so we no longer need to memoize, because it will
      // never be thrown again.
      retryCache.delete(thenable);
    }
  
    retryTimedOutBoundary(boundaryFiber, retryTime);
  }
  
  // Computes the next Just Noticeable Difference (JND) boundary.
  // The theory is that a person can't tell the difference between small differences in time.
  // Therefore, if we wait a bit longer than necessary that won't translate to a noticeable
  // difference in the experience. However, waiting for longer might mean that we can avoid
  // showing an intermediate loading state. The longer we have already waited, the harder it
  // is to tell small differences in time. Therefore, the longer we've already waited,
  // the longer we can wait additionally. At some point we have to give up though.
  // We pick a train model where the next boundary commits at a consistent schedule.
  // These particular numbers are vague estimates. We expect to adjust them based on research.
  function jnd(timeElapsed: number) {
    return timeElapsed < 120
      ? 120
      : timeElapsed < 480
      ? 480
      : timeElapsed < 1080
      ? 1080
      : timeElapsed < 1920
      ? 1920
      : timeElapsed < 3000
      ? 3000
      : timeElapsed < 4320
      ? 4320
      : ceil(timeElapsed / 1960) * 1960;
  }
  
  function computeMsUntilSuspenseLoadingDelay(
    mostRecentEventTime: ExpirationTime,
    committedExpirationTime: ExpirationTime,
    suspenseConfig: SuspenseConfig,
  ) {
    const busyMinDurationMs = (suspenseConfig.busyMinDurationMs: any) | 0;
    if (busyMinDurationMs <= 0) {
      return 0;
    }
    const busyDelayMs = (suspenseConfig.busyDelayMs: any) | 0;
  
    // Compute the time until this render pass would expire.
    const currentTimeMs: number = now();
    const eventTimeMs: number = inferTimeFromExpirationTimeWithSuspenseConfig(
      mostRecentEventTime,
      suspenseConfig,
    );
    const timeElapsed = currentTimeMs - eventTimeMs;
    if (timeElapsed <= busyDelayMs) {
      // If we haven't yet waited longer than the initial delay, we don't
      // have to wait any additional time.
      return 0;
    }
    const msUntilTimeout = busyDelayMs + busyMinDurationMs - timeElapsed;
    // This is the value that is passed to `setTimeout`.
    return msUntilTimeout;
  }
  
  function checkForNestedUpdates() {
    // NESTED_UPDATE_LIMIT => 50
    if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
      nestedUpdateCount = 0;
      rootWithNestedUpdates = null;
      invariant(
        false,
        'Maximum update depth exceeded. This can happen when a component ' +
          'repeatedly calls setState inside componentWillUpdate or ' +
          'componentDidUpdate. React limits the number of nested updates to ' +
          'prevent infinite loops.',
      );
    }
  
    if (__DEV__) {
      if (nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT) {
        nestedPassiveUpdateCount = 0;
        console.error(
          'Maximum update depth exceeded. This can happen when a component ' +
            "calls setState inside useEffect, but useEffect either doesn't " +
            'have a dependency array, or one of the dependencies changes on ' +
            'every render.',
        );
      }
    }
  }
  
  function flushRenderPhaseStrictModeWarningsInDEV() {
    if (__DEV__) {
      ReactStrictModeWarnings.flushLegacyContextWarning();
  
      if (warnAboutDeprecatedLifecycles) {
        ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
      }
    }
  }
  
  function stopFinishedWorkLoopTimer() {
    const didCompleteRoot = true;
    stopWorkLoopTimer(interruptedBy, didCompleteRoot);
    interruptedBy = null;
  }
  
  function stopInterruptedWorkLoopTimer() {
    // TODO: Track which fiber caused the interruption.
    const didCompleteRoot = false;
    stopWorkLoopTimer(interruptedBy, didCompleteRoot);
    interruptedBy = null;
  }
  
  function checkForInterruption(
    fiberThatReceivedUpdate: Fiber,
    updateExpirationTime: ExpirationTime,
  ) {
    if (
      enableUserTimingAPI &&
      workInProgressRoot !== null &&
      updateExpirationTime > renderExpirationTime
    ) {
      interruptedBy = fiberThatReceivedUpdate;
    }
  }
  
  let didWarnStateUpdateForUnmountedComponent: Set<string> | null = null;
  function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
    if (__DEV__) {
      const tag = fiber.tag;
      if (
        tag !== HostRoot &&
        tag !== ClassComponent &&
        tag !== FunctionComponent &&
        tag !== ForwardRef &&
        tag !== MemoComponent &&
        tag !== SimpleMemoComponent &&
        tag !== Block
      ) {
        // Only warn for user-defined components, not internal ones like Suspense.
        return;
      }
  
      if (
        deferPassiveEffectCleanupDuringUnmount &&
        runAllPassiveEffectDestroysBeforeCreates
      ) {
        // If there are pending passive effects unmounts for this Fiber,
        // we can assume that they would have prevented this update.
        if (pendingPassiveHookEffectsUnmount.indexOf(fiber) >= 0) {
          return;
        }
      }
  
      // We show the whole stack but dedupe on the top component's name because
      // the problematic code almost always lies inside that component.
      const componentName = getComponentName(fiber.type) || 'ReactComponent';
      if (didWarnStateUpdateForUnmountedComponent !== null) {
        if (didWarnStateUpdateForUnmountedComponent.has(componentName)) {
          return;
        }
        didWarnStateUpdateForUnmountedComponent.add(componentName);
      } else {
        didWarnStateUpdateForUnmountedComponent = new Set([componentName]);
      }
      console.error(
        "Can't perform a React state update on an unmounted component. This " +
          'is a no-op, but it indicates a memory leak in your application. To ' +
          'fix, cancel all subscriptions and asynchronous tasks in %s.%s',
        tag === ClassComponent
          ? 'the componentWillUnmount method'
          : 'a useEffect cleanup function',
        getStackByFiberInDevAndProd(fiber),
      );
    }
  }
  
  let beginWork;
  if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
    let dummyFiber = null;
    beginWork = (current, unitOfWork, expirationTime) => {
      // If a component throws an error, we replay it again in a synchronously
      // dispatched event, so that the debugger will treat it as an uncaught
      // error See ReactErrorUtils for more information.
  
      // Before entering the begin phase, copy the work-in-progress onto a dummy
      // fiber. If beginWork throws, we'll use this to reset the state.
      const originalWorkInProgressCopy = assignFiberPropertiesInDEV(
        dummyFiber,
        unitOfWork,
      );
      try {
        return originalBeginWork(current, unitOfWork, expirationTime);
      } catch (originalError) {
        if (
          originalError !== null &&
          typeof originalError === 'object' &&
          typeof originalError.then === 'function'
        ) {
          // Don't replay promises. Treat everything else like an error.
          throw originalError;
        }
  
        // Keep this code in sync with handleError; any changes here must have
        // corresponding changes there.
        resetContextDependencies();
        resetHooksAfterThrow();
        // Don't reset current debug fiber, since we're about to work on the
        // same fiber again.
  
        // Unwind the failed stack frame
        unwindInterruptedWork(unitOfWork);
  
        // Restore the original properties of the fiber.
        assignFiberPropertiesInDEV(unitOfWork, originalWorkInProgressCopy);
  
        if (enableProfilerTimer && unitOfWork.mode & ProfileMode) {
          // Reset the profiler timer.
          startProfilerTimer(unitOfWork);
        }
  
        // Run beginWork again.
        invokeGuardedCallback(
          null,
          originalBeginWork,
          null,
          current,
          unitOfWork,
          expirationTime,
        );
  
        if (hasCaughtError()) {
          const replayError = clearCaughtError();
          // `invokeGuardedCallback` sometimes sets an expando `_suppressLogging`.
          // Rethrow this error instead of the original one.
          throw replayError;
        } else {
          // This branch is reachable if the render phase is impure.
          throw originalError;
        }
      }
    };
  } else {
    beginWork = originalBeginWork;
  }
  
  let didWarnAboutUpdateInRender = false;
  let didWarnAboutUpdateInRenderForAnotherComponent;
  if (__DEV__) {
    didWarnAboutUpdateInRenderForAnotherComponent = new Set();
  }
  
  function warnAboutRenderPhaseUpdatesInDEV(fiber) {
    if (__DEV__) {
      if (
        ReactCurrentDebugFiberIsRenderingInDEV &&
        (executionContext & RenderContext) !== NoContext
      ) {
        switch (fiber.tag) {
          case FunctionComponent:
          case ForwardRef:
          case SimpleMemoComponent: {
            const renderingComponentName =
              (workInProgress && getComponentName(workInProgress.type)) ||
              'Unknown';
            // Dedupe by the rendering component because it's the one that needs to be fixed.
            const dedupeKey = renderingComponentName;
            if (!didWarnAboutUpdateInRenderForAnotherComponent.has(dedupeKey)) {
              didWarnAboutUpdateInRenderForAnotherComponent.add(dedupeKey);
              const setStateComponentName =
                getComponentName(fiber.type) || 'Unknown';
              console.error(
                'Cannot update a component (`%s`) while rendering a ' +
                  'different component (`%s`). To locate the bad setState() call inside `%s`, ' +
                  'follow the stack trace as described in https://fb.me/setstate-in-render',
                setStateComponentName,
                renderingComponentName,
                renderingComponentName,
              );
            }
            break;
          }
          case ClassComponent: {
            if (!didWarnAboutUpdateInRender) {
              console.error(
                'Cannot update during an existing state transition (such as ' +
                  'within `render`). Render methods should be a pure ' +
                  'function of props and state.',
              );
              didWarnAboutUpdateInRender = true;
            }
            break;
          }
        }
      }
    }
  }
  
  // a 'shared' variable that changes when act() opens/closes in tests.
  export const IsThisRendererActing = {current: (false: boolean)};
  
  export function warnIfNotScopedWithMatchingAct(fiber: Fiber): void {
    if (__DEV__) {
      if (
        warnsIfNotActing === true &&
        IsSomeRendererActing.current === true &&
        IsThisRendererActing.current !== true
      ) {
        console.error(
          "It looks like you're using the wrong act() around your test interactions.\n" +
            'Be sure to use the matching version of act() corresponding to your renderer:\n\n' +
            '// for react-dom:\n' +
            "import {act} from 'react-dom/test-utils';\n" +
            '// ...\n' +
            'act(() => ...);\n\n' +
            '// for react-test-renderer:\n' +
            "import TestRenderer from 'react-test-renderer';\n" +
            'const {act} = TestRenderer;\n' +
            '// ...\n' +
            'act(() => ...);' +
            '%s',
          getStackByFiberInDevAndProd(fiber),
        );
      }
    }
  }
  
  export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
    if (__DEV__) {
      if (
        warnsIfNotActing === true &&
        (fiber.mode & StrictMode) !== NoMode &&
        IsSomeRendererActing.current === false &&
        IsThisRendererActing.current === false
      ) {
        console.error(
          'An update to %s ran an effect, but was not wrapped in act(...).\n\n' +
            'When testing, code that causes React state updates should be ' +
            'wrapped into act(...):\n\n' +
            'act(() => {\n' +
            '  /* fire events that update state */\n' +
            '});\n' +
            '/* assert on the output */\n\n' +
            "This ensures that you're testing the behavior the user would see " +
            'in the browser.' +
            ' Learn more at https://fb.me/react-wrap-tests-with-act' +
            '%s',
          getComponentName(fiber.type),
          getStackByFiberInDevAndProd(fiber),
        );
      }
    }
  }
  
  function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
    if (__DEV__) {
      if (
        warnsIfNotActing === true &&
        executionContext === NoContext &&
        IsSomeRendererActing.current === false &&
        IsThisRendererActing.current === false
      ) {
        console.error(
          'An update to %s inside a test was not wrapped in act(...).\n\n' +
            'When testing, code that causes React state updates should be ' +
            'wrapped into act(...):\n\n' +
            'act(() => {\n' +
            '  /* fire events that update state */\n' +
            '});\n' +
            '/* assert on the output */\n\n' +
            "This ensures that you're testing the behavior the user would see " +
            'in the browser.' +
            ' Learn more at https://fb.me/react-wrap-tests-with-act' +
            '%s',
          getComponentName(fiber.type),
          getStackByFiberInDevAndProd(fiber),
        );
      }
    }
  }
  
  export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;
  
  // In tests, we want to enforce a mocked scheduler.
  let didWarnAboutUnmockedScheduler = false;
  // TODO Before we release concurrent mode, revisit this and decide whether a mocked
  // scheduler is the actual recommendation. The alternative could be a testing build,
  // a new lib, or whatever; we dunno just yet. This message is for early adopters
  // to get their tests right.
  
  export function warnIfUnmockedScheduler(fiber: Fiber) {
    if (__DEV__) {
      if (
        didWarnAboutUnmockedScheduler === false &&
        Scheduler.unstable_flushAllWithoutAsserting === undefined
      ) {
        if (fiber.mode & BlockingMode || fiber.mode & ConcurrentMode) {
          didWarnAboutUnmockedScheduler = true;
          console.error(
            'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
              'to guarantee consistent behaviour across tests and browsers. ' +
              'For example, with jest: \n' +
              "jest.mock('scheduler', () => require('scheduler/unstable_mock'));\n\n" +
              'For more info, visit https://fb.me/react-mock-scheduler',
          );
        } else if (warnAboutUnmockedScheduler === true) {
          didWarnAboutUnmockedScheduler = true;
          console.error(
            'Starting from React v17, the "scheduler" module will need to be mocked ' +
              'to guarantee consistent behaviour across tests and browsers. ' +
              'For example, with jest: \n' +
              "jest.mock('scheduler', () => require('scheduler/unstable_mock'));\n\n" +
              'For more info, visit https://fb.me/react-mock-scheduler',
          );
        }
      }
    }
  }
  
  function computeThreadID(root, expirationTime) {
    // Interaction threads are unique per root and expiration time.
    return expirationTime * 1000 + root.interactionThreadID;
  }
  
  export function markSpawnedWork(expirationTime: ExpirationTime) {
    if (!enableSchedulerTracing) {
      return;
    }
    if (spawnedWorkDuringRender === null) {
      spawnedWorkDuringRender = [expirationTime];
    } else {
      spawnedWorkDuringRender.push(expirationTime);
    }
  }
  
  function scheduleInteractions(root, expirationTime, interactions) {
    if (!enableSchedulerTracing) {
      return;
    }
    if (interactions.size > 0) {
      const pendingInteractionMap = root.pendingInteractionMap;
      const pendingInteractions = pendingInteractionMap.get(expirationTime);
      if (pendingInteractions != null) {
        interactions.forEach((interaction) => {
          if (!pendingInteractions.has(interaction)) {
            // Update the pending async work count for previously unscheduled interaction.
            interaction.__count++;
          }
  
          pendingInteractions.add(interaction);
        });
      } else {
        pendingInteractionMap.set(expirationTime, new Set(interactions));
  
        // Update the pending async work count for the current interactions.
        interactions.forEach((interaction) => {
          interaction.__count++;
        });
      }
  
      const subscriber = __subscriberRef.current;
      if (subscriber !== null) {
        const threadID = computeThreadID(root, expirationTime);
        subscriber.onWorkScheduled(interactions, threadID);
      }
    }
  }
  
  function schedulePendingInteractions(root, expirationTime) {
    // This is called when work is scheduled on a root.
    // It associates the current interactions with the newly-scheduled expiration.
    // They will be restored when that expiration is later committed.
    if (!enableSchedulerTracing) {
      return;
    }
    scheduleInteractions(root, expirationTime, __interactionsRef.current);
  }
  
  function startWorkOnPendingInteractions(root, expirationTime) {
    // This is called when new work is started on a root.
    if (!enableSchedulerTracing) {
      return;
    }
  
    // Determine which interactions this batch of work currently includes, So that
    // we can accurately attribute time spent working on it, And so that cascading
    // work triggered during the render phase will be associated with it.
    const interactions: Set<Interaction> = new Set();
    root.pendingInteractionMap.forEach(
      (scheduledInteractions, scheduledExpirationTime) => {
        if (scheduledExpirationTime >= expirationTime) {
          scheduledInteractions.forEach((interaction) =>
            interactions.add(interaction),
          );
        }
      },
    );
  
    // Store the current set of interactions on the FiberRoot for a few reasons:
    // We can re-use it in hot functions like performConcurrentWorkOnRoot()
    // without having to recalculate it. We will also use it in commitWork() to
    // pass to any Profiler onRender() hooks. This also provides DevTools with a
    // way to access it when the onCommitRoot() hook is called.
    root.memoizedInteractions = interactions;
  
    if (interactions.size > 0) {
      const subscriber = __subscriberRef.current;
      if (subscriber !== null) {
        const threadID = computeThreadID(root, expirationTime);
        try {
          subscriber.onWorkStarted(interactions, threadID);
        } catch (error) {
          // If the subscriber throws, rethrow it in a separate task
          scheduleCallback(ImmediatePriority, () => {
            throw error;
          });
        }
      }
    }
  }
  
  function finishPendingInteractions(root, committedExpirationTime) {
    if (!enableSchedulerTracing) {
      return;
    }
  
    const earliestRemainingTimeAfterCommit = root.firstPendingTime;
  
    let subscriber;
  
    try {
      subscriber = __subscriberRef.current;
      if (subscriber !== null && root.memoizedInteractions.size > 0) {
        const threadID = computeThreadID(root, committedExpirationTime);
        subscriber.onWorkStopped(root.memoizedInteractions, threadID);
      }
    } catch (error) {
      // If the subscriber throws, rethrow it in a separate task
      scheduleCallback(ImmediatePriority, () => {
        throw error;
      });
    } finally {
      // Clear completed interactions from the pending Map.
      // Unless the render was suspended or cascading work was scheduled,
      // In which case– leave pending interactions until the subsequent render.
      const pendingInteractionMap = root.pendingInteractionMap;
      pendingInteractionMap.forEach(
        (scheduledInteractions, scheduledExpirationTime) => {
          // Only decrement the pending interaction count if we're done.
          // If there's still work at the current priority,
          // That indicates that we are waiting for suspense data.
          if (scheduledExpirationTime > earliestRemainingTimeAfterCommit) {
            pendingInteractionMap.delete(scheduledExpirationTime);
  
            scheduledInteractions.forEach((interaction) => {
              interaction.__count--;
  
              if (subscriber !== null && interaction.__count === 0) {
                try {
                  subscriber.onInteractionScheduledWorkCompleted(interaction);
                } catch (error) {
                  // If the subscriber throws, rethrow it in a separate task
                  scheduleCallback(ImmediatePriority, () => {
                    throw error;
                  });
                }
              }
            });
          }
        },
      );
    }
  }
  