It was messy and unorganized but accidentally sent out so please don’t spray me guys!

React V16 rewrites the Core react algorithm — Reconciliation. 16 was formerly known as stack Reconciler, and the rewritten piece is called Fiber Reconciler, which is called Fiber for short. Fiber helps us break down the React tree application into fiber tree units. This split unit format allows us to prioritize each different task, interrupt the update process, record which unit was updated, and then continue updating from the recorded unit later. Prior to V16, dom updates were a synchronous process that had to wait for calculations to finish.

Look at the source code to understand her for what purpose this design

Overview of Scheuler’s overall process

Overview of global variables in the scheduling process

Build the task scheduling concept

scheduleWork

  1. Find the corresponding fiberRoot node for the update
  2. Reset the stack if conditions are met
  3. If the condition is met, request work scheduling for new high-priority tasks to interrupt low-priority tasks to reset the stack

RequestWork requests work

  1. Add the system to the root scheduling queue
  2. Determine whether to update data in batches
  3. Determine the dispatch type according to expirationTime

BatchedUpdates Batch update

SetState is synchronous or asynchronous

SetState itself is synchronous, but it is asynchronous when setState is called without batch updates (isbatchingUpdates are true, performSyncWork is not immediately scheduled, just updateQueue is created). SetState is not updated immediately. If it is updated immediately, setState may be updated immediately. In asynchronous rendering conditions, such as concurrentModel and aSyncModel, the asynchronous scheduling process will not be updated immediately.

reactScheduler

scheduleCallbackWithExpirationTime

  1. Maintenance time slice
  2. Simulation requestIdleCallback
  3. Scheduling list and timeout determination

scheduleCallback

Insert into a one-way list according to ExpirationTime

ensureHostCallbackIsScheduled

requestHostCallback

Not in the browser environment

RequestIdelCallback: after the callback is called by requestAnimationFrame, the task is inserted into the task queue, and the task queue is called after the browser has executed. This time adds up to 33ms

  1. The react async update allows the browser to prioritize animations and user feedback and wait for the rest of the time to perform react async updates.
  2. By timing to control the judgment of browser refresh frequency, to prevent the browser refresh only within 33ms 20ms do not update, the rest of the time to update all this kind of lag.
  3. Check whether the task is expired.

todo:

  1. Understand requestAnimationFrame
  2. Simulation requestIdelCallback

flushWork

performWork

  1. Is there a deadline
  2. Loop render root’s condition
  3. Over time slice processing

Understanding the React Fiber

Fiber’s solution: Break up the render update process into multiple sub-tasks, do a small part at a time, see if there is time left, and if there is, move on to the next task; If not, suspend the current task, give time control to the main thread, and continue executing it when the main thread is less busy.

ReactRoot created at the same time will create a fiberRoot fiberRoot. Current is a fiber object, fiberRoot on recording the highest and the lowest priority task. Connect each fiber unit via Sibling and next according to the one-way linked list.

How is each fiber unit created?

renderRoot

  • Call workLoop for loop unit updates
  • Catch the error and process it
  • Clean up after the process eg. Different types of errors. Task a hang
  • commit root
function renderRoot(
  root: FiberRoot,
  expirationTime: ExpirationTime,
  isSync: boolean.) :SchedulerCallback | null {
  invariant(
    (executionContext & (RenderContext | CommitContext)) === NoContext,
    'Should not already be working.',);if (root.firstPendingTime < expirationTime) {
    // If there's no work left at this expiration time, exit immediately. This
    // happens when multiple callbacks are scheduled for a single root, but an
    // earlier callback flushes the work of a later one.
    return null;
  }

  if (isSync && root.finishedExpirationTime === expirationTime) {
    // There's already a pending commit at this expiration time.
    // TODO: This is poorly factored. This case only exists for the
    // batch.commit() API.
    return commitRoot.bind(null, root);
  }

  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); }else if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
    // We could've received an update at a lower priority while we yielded.
    // We're suspended in a delayed state. Once we complete this render we're
    // just going to try to recover at the last pending time anyway so we might
    // as well start doing that eagerly.
    // Ideally we should be able to do this even for retries but we don't yet
    // know if we're going to process an update which wants to commit earlier,
    // and this path happens very early so it would happen too often. Instead,
    // for that case, we'll wait until we complete.
    if (workInProgressRootHasPendingPing) {
      // We have a ping at this expiration. Let's restart to see if we get unblocked.
      prepareFreshStack(root, expirationTime);
    } else {
      const lastPendingTime = root.lastPendingTime;
      if (lastPendingTime < expirationTime) {
        // There's lower priority work. It might be unsuspended. Try rendering
        // at that level immediately, while preserving the position in the queue.
        return renderRoot.bind(null, root, lastPendingTime); }}}// 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;
    let prevDispatcher = ReactCurrentDispatcher.current;
    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.
      prevDispatcher = ContextOnlyDispatcher;
    }
    ReactCurrentDispatcher.current = ContextOnlyDispatcher;
    let prevInteractions: Set<Interaction> | null = null;
    if (enableSchedulerTracing) {
      prevInteractions = __interactionsRef.current;
      __interactionsRef.current = root.memoizedInteractions;
    }

    startWorkLoopTimer(workInProgress);

    // TODO: Fork renderRoot into renderRootSync and renderRootAsync
    if (isSync) {
      if(expirationTime ! == Sync) {// An async update expired. There may be other expired updates on
        // this root. We should render all the expired work in a
        // single batch.
        const currentTime = requestCurrentTime();
        if (currentTime < expirationTime) {
          // Restart at the current time.
          executionContext = prevExecutionContext;
          resetContextDependencies();
          ReactCurrentDispatcher.current = prevDispatcher;
          if (enableSchedulerTracing) {
            __interactionsRef.current = ((prevInteractions: any): Set<
              Interaction,
            >);
          }
          return renderRoot.bind(null, root, currentTime); }}}else {
      // 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;
    }

    do {
      try {
        if (isSync) {
          workLoopSync();
        } else {
          workLoop();
        }
        break;
      } catch (thrownValue) {
        // Reset module-level state that was set during the render phase.
        resetContextDependencies();
        resetHooks();

        const sourceFiber = workInProgress;
        if (sourceFiber === null || sourceFiber.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.
          prepareFreshStack(root, expirationTime);
          executionContext = prevExecutionContext;
          throw thrownValue;
        }

        if (enableProfilerTimer && sourceFiber.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(sourceFiber, true);
        }

        constreturnFiber = sourceFiber.return; throwException( root, returnFiber, sourceFiber, thrownValue, renderExpirationTime, ); workInProgress = completeUnitOfWork(sourceFiber); }}while (true);

    executionContext = prevExecutionContext;
    resetContextDependencies();
    ReactCurrentDispatcher.current = prevDispatcher;
    if (enableSchedulerTracing) {
      __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);
    }

    if(workInProgress ! = =null) {
      // There's still work left over. Return a continuation.
      stopInterruptedWorkLoopTimer();
      return renderRoot.bind(null, root, expirationTime); }}// 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();

  root.finishedWork = root.current.alternate;
  root.finishedExpirationTime = expirationTime;

  const isLocked = resolveLocksOnRoot(root, expirationTime);
  if (isLocked) {
    // This root has a lock that prevents it from committing. Exit. If we begin
    // work on the root again, without any intervening updates, it will finish
    // without doing additional work.
    return null;
  }

  // Set this to null to indicate there's no in-progress render.
  workInProgressRoot = null;

  switch (workInProgressRootExitStatus) {
    case RootIncomplete: {
      invariant(false.'Should have a work-in-progress.');
    }
    // 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: {
      // An error was thrown. First check if there is lower priority work
      // scheduled on this root.
      const lastPendingTime = root.lastPendingTime;
      if (lastPendingTime < expirationTime) {
        // There's lower priority work. Before raising the error, try rendering
        // at the lower priority to see if it fixes it. Use a continuation to
        // maintain the existing priority and position in the queue.
        return renderRoot.bind(null, root, lastPendingTime);
      }
      if(! isSync) {// If we're rendering asynchronously, it's possible the error was
        // caused by tearing due to a mutation during an event. Try rendering
        // one more time without yiedling to events.
        prepareFreshStack(root, expirationTime);
        scheduleSyncCallback(renderRoot.bind(null, root, expirationTime));
        return null;
      }
      // If we're already rendering synchronously, commit the root in its
      // errored state.
      return commitRoot.bind(null, root);
    }
    case RootSuspended: {
      flushSuspensePriorityWarningInDEV();

      // 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 && ! isSync &&// 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) {
            // This render was pinged but we didn't get to restart earlier so try
            // restarting now instead.
            prepareFreshStack(root, expirationTime);
            return renderRoot.bind(null, root, expirationTime);
          }
          const lastPendingTime = root.lastPendingTime;
          if (lastPendingTime < expirationTime) {
            // There's lower priority work. It might be unsuspended. Try rendering
            // at that level.
            return renderRoot.bind(null, root, lastPendingTime);
          }
          // 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,
          );
          return null; }}// The work expired. Commit immediately.
      return commitRoot.bind(null, root);
    }
    case RootSuspendedWithDelay: {
      flushSuspensePriorityWarningInDEV();

      if (
        !isSync &&
        // 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) {
          // This render was pinged but we didn't get to restart earlier so try
          // restarting now instead.
          prepareFreshStack(root, expirationTime);
          return renderRoot.bind(null, root, expirationTime);
        }
        const lastPendingTime = root.lastPendingTime;
        if (lastPendingTime < expirationTime) {
          // There's lower priority work. It might be unsuspended. Try rendering
          // at that level immediately.
          return renderRoot.bind(null, root, lastPendingTime);
        }

        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,
          );
          return null; }}// The work expired. Commit immediately.
      return commitRoot.bind(null, root);
    }
    case RootCompleted: {
      // The work completed. Ready to commit.
      if (
        !isSync &&
        // 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) {
          root.timeoutHandle = scheduleTimeout(
            commitRoot.bind(null, root),
            msUntilTimeout,
          );
          return null; }}return commitRoot.bind(null, root);
    }
    default: {
      invariant(false.'Unknown root exit status.'); }}}Copy the code

supplement

expirationTime

  • Processed recomputeCurrentRenderTime before computation time