React source series

  • Create the Fiber structure for React source parsing
  • React Fiber (1)
  • React code parsing of Fiber (2) beginWork

We got the fiberRoot structure from the React Fiber Structure creation, and this post will focus on how to render the fiberRoot into the view.

How do I generate the workInProgress structure

The previous part generated a fiberRoot by looking at the call stack and started parsing the fiberRoot structure by calling the updateContainer function as the entry function.

updateContainer(children, fiberRoot, parentComponent, callback);
Copy the code

The parentComponent is the container (div#root) in react.render.

UpdateContainer handles several things.

1. Obtain the current prioritylane(lane)

  const current = container.current;//container - fiber root
  // Get the priority lane
  const lane = requestUpdateLane(current);
Copy the code

Lane is 1.

After React V17, lane is the priority. The smaller the lane value, the higher the priority. Lane is stored in binary, with 31 bits in total, and each bit is a lane. Click here to see the source code.

export const TotalLanes = 31;

export const NoLanes: Lanes = / * * / 0b0000000000000000000000000000000;
export const NoLane: Lane = / * * / 0b0000000000000000000000000000000;

export const SyncLane: Lane = / * * / 0b0000000000000000000000000000001;

// The input box value
const InputContinuousHydrationLane: Lane = / * * / 0b0000000000000000000000000000010;
export const InputContinuousLane: Lanes = / * * / 0b0000000000000000000000000000100;

export const DefaultHydrationLane: Lane = / * * / 0b0000000000000000000000000001000;
export const DefaultLane: Lanes = / * * / 0b0000000000000000000000000010000;

Copy the code

In requestUpdateLane, when the render is initialized, lane is 1.

export function requestUpdateLane(fiber: Fiber) :Lane {
  // Special cases
  const mode = fiber.mode;
  if ((mode & ConcurrentMode) === NoMode) {
    // 0b0000000000000000000000000000001;
    // Lane 1 is priority 1
    return (SyncLane: Lane);
    / /...
Copy the code

In the React source code, there is a separate file that holds the binary values for the different types of prioritized lanes. There’s a separate article on Lane that explains what it means, and how to use lane for permission design, but for now let’s just know that this place gets priority for Lane as a fiber.

Create context.

During initialization, the function getContextForSubtree is used to determine whether parentComponent exists. ParentComponet is the parent element of root and null at this time. Otherwise, parentContext is returned.

function getContextForSubtree(parentComponent: ? React$Component
       
        ,
       ,>) :Object {
  if(! parentComponent) {return emptyContextObject;
  }

  const fiber = getInstance(parentComponent);
  const parentContext = findCurrentUnmaskedContext(fiber);

  if (fiber.tag === ClassComponent) {
    const Component = fiber.type;
    if (isLegacyContextProvider(Component)) {
      returnprocessChildContext(fiber, Component, parentContext); }}return parentContext;
}
Copy the code

Create an UPDATE

Create an update object for enqueueUpdate.

// Create an update object
  const update = createUpdate(eventTime, lane);
  // ...
  export function createUpdate(eventTime: number, lane: Lane) :Update< * >{
  const update: Update<*> = {
    eventTime,
    lane,
    tag: UpdateState,/ / tag to 0 | 2 | 3 | 1
    payload: null.callback: null.next: null};return update;
}
// ...
update.payload = { element };
callback = callback === undefined ? null : callback;
update.callback = callback;
}
// Update the queue
enqueueUpdate(current, update, lane);
Copy the code

EnqueueUpdate is used to add a sharedQueue, sharedQueue, that can be shared with workInProgress and FiberRoot.

export function enqueueUpdate<State> (
  fiber: Fiber,//fiberRoot
  update: Update<State>,
  lane: Lane,
) {
  const updateQueue = fiber.updateQueue;
  if (updateQueue === null) {
    // Only occurs if the fiber has been unmounted.
    return;
  }
  // The current queue and the cache queue share a persistent queue
  const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
  // Compare fiber lane and lane, and update the same
  // Render is not initialized
  if (isInterleavedUpdate(fiber, lane)) {
    const interleaved = sharedQueue.interleaved;// Interleaved updates
    if (interleaved === null) {
      // If this is the first update, create a bidirectional list
      update.next = update;
      // Interleaved updates to this queue will be displayed when the current render ends
      // Is transferred to the suspended queue.
      pushInterleavedQueue(sharedQueue);
    } else {
      // interleaved.next -> update.next update - interleaved.next;
      // interleaved.next = update
      // update.next = interleaved.next = update
      update.next = interleaved.next;
      interleaved.next = update;
    }
    sharedQueue.interleaved = update;
  } else {
    const pending = sharedQueue.pending;
    if (pending === null) {
      // This is the first update. Create a unidirectional linked list.
      update.next = update;
    } else {
      // Define a bidirectional listupdate.next = pending.next; pending.next = update; } sharedQueue.pending = update; }}Copy the code

Fourth, callscheduleUpdateOnFiber

In this case, we only look at the logic that the render function executes. When the page is initialized, only fiberRoot is generated, not workInProgressRoot, which is null. Therefore, It’s going to do a little bit of logic, call performSyncWorkOnRoot.

export function scheduleUpdateOnFiber(
  fiber: Fiber,
  lane: Lane,// Update lane currently
  eventTime: number,
) :FiberRoot | null {...// Mark root as having pending updates.
  markRootUpdated(root, lane, eventTime);
  if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
    ...
  }
    / / root for fiber, workInfoProGressRoot is null, to false
  if (root === workInProgressRoot) {
   ...
  }

  if (lane === SyncLane) {
    if (
      // Check whether we are on unbatchedUpdates(executionContext & LegacyUnbatchedContext) ! == NoContext &&// Check if we haven't rendered yet
      (executionContext & (RenderContext | CommitContext)) === NoContext
    ) {
      // Register pending interactions at the root directory to avoid losing traced interaction data.
      schedulePendingInteractions(root, lane);
      performSyncWorkOnRoot(root);
    } else {
      ensureRootIsScheduled(root, eventTime);
      schedulePendingInteractions(root, lane);
      if (
        executionContext === NoContext &&
        (fiber.mode & ConcurrentMode) === NoMode
      ) {
      / / resetresetRenderTimer(); flushSyncCallbackQueue(); }}}else {
    / /...
    ensureRootIsScheduled(root, eventTime);
    schedulePendingInteractions(root, lane);
  }

  return root;
}
Copy the code

PerformSyncWorkOnRoot refreshes Effects and synchronizes the render Fiber.

/ /...
  if (
    root === workInProgressRoot &&
    areLanesExpired(root, workInProgressRootRenderLanes)
  ) {
    lanes = workInProgressRootRenderLanes;
    exitStatus = renderRootSync(root, lanes);
  } else {
    lanes = getNextLanes(root, NoLanes);
    // Render root synchronously
    exitStatus = renderRootSync(root, lanes);
  }
 / /...
Copy the code

The renderRootSync function first creates a bidirectional list tree of workInProgress associated through alternate and fiberRoot

.if(workInProgressRoot ! == root || workInProgressRootRenderLanes ! == lanes) {// Create a tree for workInProgress
    prepareFreshStack(root, lanes);
    / / handle hang interaction, and there will be interactive, binding to root in the store. The memoizedInteractionsstartWorkOnPendingInteractions(root, lanes); }...// Create workInProgress and workInProgressRoot.
 function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
  root.finishedWork = null;
  root.finishedLanes = NoLanes;

  const timeoutHandle = root.timeoutHandle;
  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);
  }

  if(workInProgress ! = =null) {
    let interruptedWork = workInProgress.return;
    while(interruptedWork ! = =null) {
      unwindInterruptedWork(interruptedWork, workInProgressRootRenderLanes);
      interruptedWork = interruptedWork.return;
    }
  }
  workInProgressRoot = root;
  workInProgress = createWorkInProgress(root.current, null);// Create workInProgess based on the current node
  workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
  workInProgressRootExitStatus = RootIncomplete;
  workInProgressRootFatalError = null; workInProgressRootSkippedLanes = NoLanes; workInProgressRootUpdatedLanes = NoLanes; workInProgressRootPingedLanes = NoLanes; . }Copy the code

Execute the subsequent code, call the workLoopSync function, start to process the workInProgress two-way linked list, and enter the beginWork phase.

  do {
    try {
      workLoopSync();// Update synchronously
      break;
    } catch(thrownValue) { handleError(root, thrownValue); }}while (true); . .function workLoopSync() {
      // Already timed out, so perform work without checking if we need to yield.
      while(workInProgress ! = =null) {
        performUnitOfWork(workInProgress);// Execute the unit of work}}...Copy the code

At this time, the workInProgress structure has been generated, and its structure is consistent with the left structure. I omitted the drawing here and connected through the alternate in the middle. Figure is as follows:

The function call stack is as follows:

Next article: beginWork stage.