This is the second day of my participation in the August More text Challenge. For details, see:August is more challenging

This article is based on the latest stable version V17.0.2

An overview of

Through the introduction of the macro package structure and the two major working cycles above, we have a certain understanding of the React-Reconciler package.

The main functions of the React-Reconciler package are summarized here, and the main functions are divided into four aspects:

  1. Input: ExposureapiFunctions (e.g.scheduleUpdateOnFiber) to supply other packages (e.greactPackage).
  2. Registering scheduling tasks: With the scheduling center (schedulerPackage) to register for scheduling taskstask, waiting for the task callback.
  3. Execution task callback: constructed in memoryFiber tree, while working with the renderer (react-dom) in memory to create andfiberThe correspondingDOMNode.
  4. Output: With the renderer (react-dom) interaction, renderingDOMNode.

The above source code is concentrated in ReactFiberWorkloop.js. Now string these functions together (from input to output), as shown in the following figure:

The 1,2,3, and 4 steps in the figure reflect the react-Reconciler package’s process from input to output, which is a fixed process that runs every time it is updated.

decomposition

Only the core function call relationships are listed in the diagram (each step has its own implementation details, which will be explored in subsequent sections). Break down each of the four steps to understand their main logic.

The input

In reactFiberWorkloop. js, the only function that accepts input is the scheduleUpdateOnFiber source address. In the React-Reconciler’s exposed API, scheduleUpdateOnFiber is invoked indirectly whenever an operation (either first rendering or subsequent update) is required to change the fiber. So the scheduleUpdateOnFiber function is the only way in the input link.

// The only function that receives input signals
export function scheduleUpdateOnFiber(fiber: Fiber, lane: Lane, eventTime: number,) {
  / /... Omit some irrelevant code
  const root = markUpdateLaneFromFiberToRoot(fiber, lane);
  if (lane === SyncLane) {
    if( (executionContext & LegacyUnbatchedContext) ! == NoContext && (executionContext & (RenderContext | CommitContext)) === NoContext ) {// Direct 'fiber structure'
      performSyncWorkOnRoot(root);
    } else {
      // Register the scheduling task, which is scheduled by the 'Scheduler' package, and then 'fiber construct' indirectlyensureRootIsScheduled(root, eventTime); }}else {
    // Register the scheduling task, which is scheduled by the 'Scheduler' package, and then 'fiber construct' indirectlyensureRootIsScheduled(root, eventTime); }}Copy the code

ScheduleUpdateOnFiber ();

  1. No scheduling, directFiber structure.
  2. Register the scheduling task, afterSchedulerPackage scheduling, indirectFiber structure.

Registering a Scheduling Task

Tightly connected to the input link, the “scheduleUpdateOnFiber” function, and then immediately go to the ensureRootIsScheduled function:

/ /... Omit some irrelevant code
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  // The first half: determine whether a new schedule needs to be registered
  const existingCallbackNode = root.callbackNode;
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  const newCallbackPriority = returnNextLanesPriority();
  if (nextLanes === NoLanes) {
    return;
  }
  if(existingCallbackNode ! = =null) {
    const existingCallbackPriority = root.callbackPriority;
    if (existingCallbackPriority === newCallbackPriority) {
      return;
    }
    cancelCallback(existingCallbackNode);
  }

  // Second half: register the scheduling task
  let newCallbackNode;
  if (newCallbackPriority === SyncLanePriority) {
    newCallbackNode = scheduleSyncCallback(
      performSyncWorkOnRoot.bind(null, root),
    );
  } else if (newCallbackPriority === SyncBatchedLanePriority) {
    newCallbackNode = scheduleCallback(
      ImmediateSchedulerPriority,
      performSyncWorkOnRoot.bind(null, root),
    );
  } else {
    const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
      newCallbackPriority,
    );
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }
  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}
Copy the code

The logic of ensureRootIsScheduled is clear, and it’s divided into 2 parts:

  1. The first half: Determines whether a new schedule needs to be registered (exits the function if no new schedule is needed)
  2. Second half: Register the scheduling task
    • performSyncWorkOnRootorperformConcurrentWorkOnRootIs encapsulated into the task callback (scheduleCallback)
    • Waiting for the scheduling center to execute the task, task running is actually executionperformSyncWorkOnRootorperformConcurrentWorkOnRoot

Perform a task callback

Task callback, is actually perform performSyncWorkOnRoot or performConcurrentWorkOnRoot. Taking a quick look at their source code (which we’ll delve into in the Fiber Tree construction section), you can strip out the main logic, and the amount of code for a single function isn’t much.

performSyncWorkOnRoot:

/ /... Omit some irrelevant code
function performSyncWorkOnRoot(root) {
  let lanes;
  let exitStatus;

  lanes = getNextLanes(root, NoLanes);
  // 1. Fiber tree structure
  exitStatus = renderRootSync(root, lanes);

  // 2. Exception handling: An exception may occur during the construction of the fiber
  if(root.tag ! == LegacyRoot && exitStatus === RootErrored) {// ...
  }

  // 3. Output: Render the Fiber tree
  const finishedWork: Fiber = (root.current.alternate: any);
  root.finishedWork = finishedWork;
  root.finishedLanes = lanes;
  commitRoot(root);

  // Before exiting, check again to see if there are any other updates and whether a new schedule needs to be initiated
  ensureRootIsScheduled(root, now());
  return null;
}
Copy the code

The logic of performSyncWorkOnRoot is clear and divided into three parts:

  1. Fiber tree structure
  2. Exception handling: It is possible that an exception occurred during the construction of fiber
  3. Call the output

performConcurrentWorkOnRoot

/ /... Omit some irrelevant code
function performConcurrentWorkOnRoot(root) {

  const originalCallbackNode = root.callbackNode;

  // 1. Refresh the effects of pending state. It is possible that some effects will cancel the task
  const didFlushPassiveEffects = flushPassiveEffects();
  if (didFlushPassiveEffects) {
    if(root.callbackNode ! == originalCallbackNode) {// The task is cancelled, exit the call
      return null;
    } else {
      // Current task was not canceled. Continue.}}// 2. Get the priority of the render
  let lanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  // 3. Construct the fiber tree
  let exitStatus = renderRootConcurrent(root, lanes);

  if (
    includesSomeLane(
      workInProgressRootIncludedLanes,
      workInProgressRootUpdatedLanes,
    )
  ) {
    // If a new update is generated in Render, and the priority of the new update intersects with the priority of the original Render
    // Then the initial render is invalid, discarding the initial render result and waiting for the next dispatch
    prepareFreshStack(root, NoLanes);
  } else if(exitStatus ! == RootIncomplete) {// 4. Exception handling: An exception may occur during the construction of the fiber
    if (exitStatus === RootErrored) {
      // ...
    }.
    const finishedWork: Fiber = (root.current.alternate: any);
    root.finishedWork = finishedWork;
    root.finishedLanes = lanes;
    // 5. Output: Render the Fiber tree
    finishConcurrentRender(root, exitStatus, lanes);
  }

  // Before exiting, check again to see if there are any other updates and whether a new schedule needs to be initiated
  ensureRootIsScheduled(root, now());
  if (root.callbackNode === originalCallbackNode) {
    / / rendering is blocked, return a new performConcurrentWorkOnRoot function, wait for the next call
    return performConcurrentWorkOnRoot.bind(null, root);
  }
  return null;
}
Copy the code

PerformConcurrentWorkOnRoot logic and performSyncWorkOnRoot differs, support for the interruptible rendering:

  1. callperformConcurrentWorkOnRootFunction, first check to see if therenderWhether to restore the last rendering.
  2. If the rendering is interrupted, and finally return a new performConcurrentWorkOnRoot function, wait for the next call.

The output

commitRoot:

/ /... Omit some irrelevant code
function commitRootImpl(root, renderPriorityLevel) {
  // Set local variables
  const finishedWork = root.finishedWork;
  const lanes = root.finishedLanes;

  // Clear the FiberRoot object properties
  root.finishedWork = null;
  root.finishedLanes = NoLanes;
  root.callbackNode = null;

  // Commit phase
  let firstEffect = finishedWork.firstEffect;
  if(firstEffect ! = =null) {
    const prevExecutionContext = executionContext;
    executionContext |= CommitContext;
    // Stage 1: before the DOM mutation
    nextEffect = firstEffect;
    do {
      commitBeforeMutationEffects();
    } while(nextEffect ! = =null);

    // Stage 2: DOM mutation, interface change
    nextEffect = firstEffect;
    do {
      commitMutationEffects(root, renderPriorityLevel);
    } while(nextEffect ! = =null);
    root.current = finishedWork;

    // Phase 3: Layout phase, calling life cycle componentDidUpdate and callback functions
    nextEffect = firstEffect;
    do {
      commitLayoutEffects(root, lanes);
    } while(nextEffect ! = =null);
    nextEffect = null;
    executionContext = prevExecutionContext;
  }
  ensureRootIsScheduled(root, now());
  return null;
}
Copy the code

In the output phase, the implementation logic of commitRoot is in the commitRootImpl function, where the main logic is to process the side effect queue and reflect the latest fiber tree structure to the DOM.

The core logic is divided into three steps:

  1. commitBeforeMutationEffects
    • Before DOM changes, the main processing side effects queue withSnapshot.PassiveOf the tagfiberNode.
  2. commitMutationEffects
    • The DOM changes and the interface is updated. Main processing side effects with queuePlacement.Update.Deletion.HydratingOf the tagfiberNode.
  3. commitLayoutEffects
    • After dom changes, the main side effects are processed in the queue withUpdate | CallbackOf the tagfiberNode.

conclusion

This section analyzes the operation process of the Reconciler from a macro perspective and divides it into four steps, which basically covers the core logic of the React-Reconciler package.

Write in the last

This article belongs to the diagram react source code series in the operation of the core plate, this series of nearly 20 articles, not for the interview, really is to understand the React source code, and then improve the architecture and coding ability.

The first draft of the graphic section has been completed and will be updated in August. If there are any errors in the article, we will correct them as soon as possible on Github.