The sea is never quiet. He hits the shore, endlessly searching like a young heart. — Carl Sandburg

  • Wechat official account “JavaScript Full Stack”
  • Nuggets’ Master of One ‘
  • Bilibili, The Master of One
  • Github address: github.com/Walker-Leee…

Everyone will eventually become cannon fodder in the course of historical evolution. It’s a vicious circle of anxiety and imperfection that permeates the Internet today.

React design system is like human society. The moment you flip the time wheel, you become a grain of sand in the wheel. Are you irreplaceable? Do you have special access? No, I’m sorry. Please keep going through the loop. You belong to the fire of life, came to mourn a lot of people, this scene, like the birth of the scene.

What the hell? This is a technical article, not a lyrical essay! Let’s get down to business.

Preparation for creation, as explained in the previous section, focuses on defining update-related data structures and variables, calculating expiration times, and so on. After these preparations are completed, the scheduling work is officially started. The implementation idea of the scheduling process is as follows: When the API related to update or mount is called, the update logic will be executed, and the update can be roughly divided into the following small stages

scheduleWork

The main tasks of this step are as follows

  1. throughscheduleWorkOnParentPathMethod to find the currentFiberThe root node
  2. Iterate over each node on the parent node of the current update node, comparing the values of each nodeexpirationTime, if greater than the current node, the value is assigned to the current nodeexpirationTimeValue. At the same time,childExpirationTimeThe value of is also the logic of
export function scheduleUpdateOnFiber(
  fiber: Fiber,
  expirationTime: ExpirationTime,
) {
  checkForNestedUpdates();
  warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);

  const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
  if (root === null) {
    warnAboutUpdateOnUnmountedFiberInDEV(fiber);
    return;
  }

  checkForInterruption(fiber, expirationTime);
  recordScheduleUpdate();

  // TODO: computeExpirationForFiber also reads the priority. Pass the
  // priority as an argument to that function and this one.
  const priorityLevel = getCurrentPriorityLevel();

  if (expirationTime === Sync) {
    if (
      // Check if we're inside unbatchedUpdates (executionContext & LegacyUnbatchedContext) ! == NoContext && // Check if we're not already rendering
      (executionContext & (RenderContext | CommitContext)) === NoContext
    ) {
      // Register pending interactions on the root to avoid losing traced interaction data.
      schedulePendingInteractions(root, expirationTime);

      performSyncWorkOnRoot(root);
    } else {
      ensureRootIsScheduled(root);
      schedulePendingInteractions(root, expirationTime);
      if(executionContext === NoContext) { flushSyncCallbackQueue(); }}}else{ ensureRootIsScheduled(root); schedulePendingInteractions(root, expirationTime); }... }export const scheduleWork = scheduleUpdateOnFiber;
Copy the code

If the expiration time is equal to the Sync constant we defined, then we further determine the status of this update. If not batchUpdates, when is not this status? As we have seen before, for example, in reder, after determining this state, we also need to ensure that the update rendering is ready, and then start processing. Before processing, however, there is another operation called Pending Interaction, where the content data related to our action needs to be stored in the Pending InteractionMap.

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) {
      constthreadID = computeThreadID(root, expirationTime); subscriber.onWorkScheduled(interactions, threadID); }}}Copy the code

So once you’ve done that, you can go to performSyncWorkOnRoot

function performSyncWorkOnRoot(root) {
  // Check if there's expired work on this root. Otherwise, render at Sync.
  const lastExpiredTime = root.lastExpiredTime;
  constexpirationTime = lastExpiredTime ! == NoWork ? lastExpiredTime : Sync;if(root.finishedExpirationTime === expirationTime) { commitRoot(root); }... }Copy the code

Okay, so now we’re done scheduling a context Time that is Sync and not unbatchedUpdates, and we find that this pipeline is easy to understand. Okay, now we’re going to go to another branch, which is batchedUpdates

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();
}
Copy the code

One ensureRootIsScheduled method is used to create scheduling tasks with one ensureRootIsScheduled for Root and one task per Root. If one of the “ensureRootIsScheduled” methods exists, that is, scheduling has already been implemented. Then we need to re-evaluate some values for this task. And then there are also a schedulePendingInteractions, to deal with interaction caused by updates, way and the above mentioned pending interaction.

In addition, if — an optional executionContext for NoContext, you may need to refresh for process synchronization update flushSyncCallbackQueue callback queue, the method is defined in SchedulerWithReactIntegration. Js.

Again and again, the update scheduling process is completed, and finally performSyncWorkOnRoot is called to proceed to the next stage

performSyncWorkOnRoot

For the same multiple choice question, can I directly submit the update now? Yes or no?

if (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.
  commitRoot(root);
}
Copy the code

It’s a rare case, and it usually goes into the else part of the judgment, which is

. workLoopSync(); . function workLoopSync() {// Already timed out, so perform work without checking if we need to yield.
  while(workInProgress ! = =null) { workInProgress = performUnitOfWork(workInProgress); }}Copy the code

Alternate unitOfWork. Alternate for comparison and staging of node attributes

function performUnitOfWork(unitOfWork: Fiber) :Fiber | null {
  // The current, flushed, state of this fiber is the alternate. Ideally
  // nothing should rely on this, but relying on it here means that we don't
  // need an additional field on the work in progress.
  const current = unitOfWork.alternate;

  startWorkTimer(unitOfWork);
  setCurrentDebugFiberInDEV(unitOfWork);

  let next;
  if(enableProfilerTimer && (unitOfWork.mode & ProfileMode) ! == NoMode) { startProfilerTimer(unitOfWork); next = beginWork(current, unitOfWork, renderExpirationTime); stopProfilerTimerIfRunningAndRecordDelta(unitOfWork,true);
  } else {
    next = beginWork(current, unitOfWork, renderExpirationTime);
  }

  resetCurrentDebugFiberInDEV();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    next = completeUnitOfWork(unitOfWork);
  }

  ReactCurrentOwner.current = null;
  return next;
}
Copy the code

It can be seen that after performing related operations, the update phase officially begins with the call of the beginWork function.

beginWork

The main job of this section is to update, update what? We talked about React in the first section. Typeof specifies that each of these different types of components is handled differently, using the common ClassComponent as an example.

function beginWork(current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime,) :Fiber | null {
  constupdateExpirationTime = workInProgress.expirationTime; .Copy the code

Then, first determine whether the current update node is empty. If not, execute the relevant logic

...
if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;

    if( oldProps ! == newProps || hasLegacyContextChanged() ||// Force a re-render if the implementation changed due to hot reload:(__DEV__ ? workInProgress.type ! == current.type :false)) {// If props or context changed, mark the fiber as having performed work.
      // This may be unset if the props are determined to be equal later (memo).
      didReceiveUpdate = true;
    } else if (updateExpirationTime < renderExpirationTime) {
      didReceiveUpdate = false; .Copy the code

Now I know a little bit. Is there any change in props before and after? Assign the didReceiveUpdate value based on different criteria. Then determine the component type of the current node according to the tag value of the current workInProgress, and enter different methods for processing according to different types.

switch (workInProgress.tag) {
	...
}
Copy the code

The update component logic is then executed, also based on that tag

case ClassComponent: {
  const Component = workInProgress.type;
  const unresolvedProps = workInProgress.pendingProps;
  const resolvedProps =
        workInProgress.elementType === Component
  ? unresolvedProps
  : resolveDefaultProps(Component, unresolvedProps);
  return updateClassComponent(
    current,
    workInProgress,
    Component,
    resolvedProps,
    renderExpirationTime,
  );
}
Copy the code

reconcileChildren

If there are child nodes during component update, schedule and update

export function reconcileChildren(current: Fiber | null, workInProgress: Fiber, nextChildren: any, renderExpirationTime: ExpirationTime,) {
  if (current === null) {
    // If this is a fresh new component that hasn't been rendered yet, we
    // won't update its child set by applying minimal side-effects. Instead,
    // we will add them all to the child before it gets rendered. That means
    // we can optimize this reconciliation pass by not tracking side-effects.
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderExpirationTime,
    );
  } else {
    // If the current child is the same as the work in progress, it means that
    // we haven't yet started any work on these children. Therefore, we use
    // the clone algorithm to create a copy of all the current children.

    // If we had any progressed work already, that is invalid at this point so
    // let's throw it out.workInProgress.child = reconcileChildFibers( workInProgress, current.child, nextChildren, renderExpirationTime, ); }}Copy the code

Fiber scheduling for its children is defined in reactChildFiber.js, which is not expanded here.

commitRoot

After completing the above scheduling process, it is also time to commit updates. This method was discussed at the beginning, skipped then, and picked up now.

function commitRoot(root) {
  const renderPriorityLevel = getCurrentPriorityLevel();
  runWithPriority(
    ImmediatePriority,
    commitRootImpl.bind(null, root, renderPriorityLevel),
  );
  return null;
}
Copy the code

The implementation is in the commitRootImpl method, which calls prepareForCommit to prepare for the update, ultimately using different strategies depending on the type of update

let primaryEffectTag =
      effectTag & (Placement | Update | Deletion | Hydrating);
switch (primaryEffectTag) {
	case Placement: {
    commitPlacement(nextEffect);
      // Clear the "placement" from effect tag so that we know that this is
      // inserted, before any life-cycles like componentDidMount gets called.
      // TODO: findDOMNode doesn't rely on this any more but isMounted does
      // and isMounted is deprecated anyway so we should be able to kill this.
      nextEffect.effectTag &= ~Placement;
      break;
    }
  case PlacementAndUpdate: {
    // Placement
    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;

    // Update
    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;
  }
  case Update: {
    const current = nextEffect.alternate;
    commitWork(current, nextEffect);
    break;
  }
  case Deletion: {
    commitDeletion(root, nextEffect, renderPriorityLevel);
    break; }}Copy the code

The processing associated with committing updates is defined in ReactFibercommitwork.js, which also uses tags to handle different strategies.

The React source code makes a lot of changes to the event system, which will be explained in detail in the following sections. The React source code design is so subtle that it’s hard to overstate it, and it’s only a cursory read. More on the source code will follow after you complete this series of cursory notes. Read source code for what?

  1. Understand how the frameworks we use every day work
  2. Learn the author NB design and the pursuit of the ultimate code, and apply it to their own projects

Ok, nonsense not to say, if you do not understand the public please move to the number or B station to view the video explanation. If you have any comments or questions, please contact me personally.

I am one. Farewell hero.