React Fiber React Fiber

After React16, the React architecture could be divided into three tiers

  • Scheduler
  • Reconciler:
  • Renderer

The Reconciler’s role is to collect the Reconciler’s changing components, ultimately allowing the Renderer to render the changing components into a page. This process of gathering the changing components is called the RENDER phase. In this phase, React iterates through the Current Fiber tree and compares the Fiber node with the corresponding React element. Construct a new fiber tree — workInProgress Fiber Tree. Today we’ll take a look at the render phase workflow.

The stage when Reconciler works is called the Render stage, and the stage when Renderer works is called the COMMIT stage

Double cache mechanism

The dual-cache mechanism is a technique that builds and replaces directly in memory. The coordination process uses this technique.

A maximum of two Fiber trees exist simultaneously in React. One is the current Fiber Tree corresponding to the DOM displayed on the screen, and the other is the workInProgress Fiber Tree constructed in memory when a new update task is triggered.

Fiber nodes in the Current Fiber Tree and workInProgress Fiber Tree are connected using the alternate property.

currentFiber.alternate === workInProgressFiber;
workInProgressFiber.alternate === currentFiber;
Copy the code

The React root node also has the Current attribute. Use the current attribute to switch between the root nodes of different Fiber trees to switch between the Current Fiber Tree and workInProgress Fiber Tree.

In the coordination phase, React uses the DIff algorithm to compare the React Element that generates the update with the corresponding nodes in the current Fiber Tree, and finally generates the workInProgress Fiber Tree in memory. The Renderer then renders the update to the page based on the workInProgress Fiber Tree. At the same time, the current attribute of the root node points to the workInProgress Fiber Tree, and the workInProgress Fiber Tree becomes the Current Fiber Tree.

Fiber Tree traversal process

Fiber tree traversal process :(don’t need to understand it completely, just need to understand the traversal process)

Function workLoopConcurrent() {// Perform tasks until Scheduler asks us to yield // While (workInProgress! == null && ! shouldYield()) { workInProgress = performUnitOfWork(workInProgress); } } function performUnitOfWork(unitOfWork: Fiber): void { //... let next; / /... Next = beginWork(current, unitOfWork, subtreeRenderLanes) next = beginWork(current, unitOfWork, subtreeRenderLanes) / /... // If there is no child node, If (next === null) {// If this doesn't spawn new work, // Inside this function will help us find the next executable node completeUnitOfWork(unitOfWork); } else { workInProgress = next; } / /... } function completeUnitOfWork(unitOfWork: Fiber): void { let completedWork = unitOfWork; do { //... Const siblingFiber = completedwork.sibling; if (siblingFiber ! == null) {// If there is more work to do in this returnFiber, do that next. Continue with performUnitOfWork, execute the current node and try traversing the child list of the current node. return; } // Otherwise, return to the parent; // If no sibling exists, go back to the parent and try to find the sibling of the parent completedWork = returnFiber; // Update the next thing we're working on in case something throws. workInProgress = completedWork; } while (completedWork ! == null); / /... }Copy the code

This traversal is actually the overall process of coordination, so let’s look in detail at how new Fiber nodes are created and how new fiber trees are constructed.

performSyncWorkOnRoot/performConcurrentWorkOnRoot

Coordinate phase entrance for performSyncWorkOnRoot (legacy mode) or performConcurrentWorkOnRoot (concurrent mode).

Function workLoopSync() {while (workInProgress! == null) { performUnitOfWork(workInProgress); }} / / performConcurrentWorkOnRoot will invoke the method function workLoopConcurrent () {while (workInProgress! == null && ! shouldYield()) { performUnitOfWork(workInProgress); }}Copy the code

These methods generate the fiber node next to workInProgress and assign the first fiber node of workInProgress to workInProgress. The new workInProgress will be connected to the created Fiber node to form the workInProgress Fiber Tree.

Both of them the only difference is in the judgment whether need to continue traversed, performConcurrentWorkOnRoot will work in judging whether there is the next unit workInProgress, on the basis of It also asks if the current browser has enough time to execute the next unit of work via the shouldYield method provided by the Scheduler module.

Traversal of three linked lists

Before fiber was introduced, React traversed nodes in depth-first mode. After Fiber was introduced, React traversed nodes in depth-first mode. After Fiber was introduced, React traversed nodes in depth-first mode.

  • Connected by fiber.childFather - > the sonTraversal of linked lists
  • Connected by fiber.returnChildren - > fatherTraversal of linked lists
  • Connected by fibre.siblingBrother - > brotherTraversal of linked lists

The traversal of these three linked lists is mainly carried out by beginWork and completeWork, and we will focus on the analysis of these two methods.

beginWork

The beginWork execution path is all the parent -> child links in the workInProgress Fiber Tree. The beginWork will create all sub-workinProgress Fiber nodes of the current workInProgress Fiber node based on the incoming fiber node (these sub-nodes will be connected via Fiber.Sibling), Connect the current workInProgress Fiber node to the first sub-WorkinProgress Fiber via the fiber.child property.

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  // ...
}
Copy the code

BeginWork accepts three parameters:

  • current: The current component is incurrent fiber treeIn the correspondingfiberNode, i.e.,workInProgress.alternate;
  • workInProgress: The current component is inworkInProgerss fiber treeIn the correspondingfiberNode, i.e.,current.alternate;
  • renderLanes: priority of render;

As we know, fiber nodes in current Fiber Tree and workInProgress Fiber Tree are connected by alternate property.

When the component is mounted, since it is the first rendering, there is no fiber node in the workInProgress Fiber Tree except the fiberRootNode, which is the fiber node in the last update. That is to say, when the component is mounted, Alternate is null for all nodes except the root node in the workInProgress Fiber Tree. Therefore, during mount, except for fiberRootNode, current is null when the other nodes call beginWork.

Update, all nodes of workInProgress Fiber Tree have the fiber node of last update, so current! = = null.

BeginWork performs branch work when mount and update. We can use current === NULL as a condition to determine whether the component is in mount or update. The subfiber node is then created in a different branch depending on the current workinProgress. tag.

function beginWork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes, ): Fiber | null { //... if (current ! == null) {//update when //... } else {// didReceiveUpdate = false; } / /... Switch (workinprogress.tag) {case IndeterminateComponent: //... Omit case LazyComponent: //... Omit case FunctionComponent: //... Omit case ClassComponent: //... Case HostRoot: //... Omit case HostComponent: //... Case HostText: //... Omit / /... Omit other types}}Copy the code

When the update beginWork

At this point, workInProgress has corresponding current node. When current and workInProgress meet certain conditions, we can reuse the child node of current node as the child node of workInProgress. Otherwise, you need to enter the process of diFF and create child nodes of workInProgress based on the results of comparison.

BeginWork relies on a didReceiveUpdate variable during fiber node creation to identify whether the current has been updated.

DidReceiveUpdate === false when the following conditions are met:

  1. No forceUpdate is used, and oldProps === newProps && workinprogress. type === current-type &&! HasLegacyContextChanged (), that is, props, fiber.type, and context are unchanged

  2. ForceUpdate is not used and! IncludesSomeLane (renderLanes, updateLanes), i.e., the priority of the current Fiber node is lower than that of the current update

const updateLanes = workInProgress.lanes; if (current ! == null) {// Update const oldProps = current. MemoizedProps; const newProps = workInProgress.pendingProps; if ( oldProps ! == newProps || hasLegacyContextChanged() || (__DEV__ ? workInProgress.type ! == current.type : false) ) { didReceiveUpdate = true; } else if (! IncludesSomeLane (renderLanes, updateLanes) {// renderLanes will not include fiber.lanes, indicating that the current fiber node priority is lower than that of this rendering. DidReceiveUpdate = false; / /... // Although the current node does not need to be updated, But you need to use bailoutOnAlreadyFinishedWork cycle test whether child nodes need to be updated return bailoutOnAlreadyFinishedWork (current, workInProgress. renderLanes); } else { if ((current.effectTag & ForceUpdateForLegacySuspense) ! == NoEffect) {// forceUpdate generates updates that need to force render didReceiveUpdate = true; } else { didReceiveUpdate = false; }}} else {//mount //... }Copy the code

When the mount beginWork

Since didReceiveUpdate is set to false directly at mount time.

const updateLanes = workInProgress.lanes; if (current ! == null) {//update when //... } else {// didReceiveUpdate = false; }Copy the code

The difference between mount and update here is mainly reflected in the assignment logic in didReceiveUpdate, as well as the diff logic for mount and update after the subsequent diff phase.

updateXXX

The beginWork will enter different branches to create sub-fiber nodes based on the current workinProgress. tag.

switch (workInProgress.tag) {
  case IndeterminateComponent: 
    // ...
  case LazyComponent: 
    // ...
  case FunctionComponent: 
    // ...
  case ClassComponent: 
    // ...
  case HostRoot:
    // ...
  case HostComponent:
    // ...
  case HostText:
    // ...
  // ...
}
Copy the code

The logic of the updateXXX function in each branch is roughly the same, and it goes through the following steps:

  1. Calculate the data of current workInProgress, such as Fiber. MemoizedState, fiber. MemoizedProps, fiber.

  2. Get the subordinate ReactElement object and set fiber. EffectTag as required.

  3. From the ReactElement object, the reconcilerChildren is called to generate a subfiber child node, and the first subfiber node is assigned to the WorkinProgress.child. At the same time, set fiber. EffectTag according to the actual situation;

Let’s take the updateHostComponent as an example. HostComponent represents native DOM element nodes (such as div, SPAN,p, etc.) whose updates go to updateHostComponent.

function updateHostComponent( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes, ) { //... MemoizedState const type = workinprogress.type; // If HostComponent is a stateless component, memoizedState const type = workinprogress.type; const nextProps = workInProgress.pendingProps; const prevProps = current ! == null ? current.memoizedProps : null; // 2. Get the lower 'ReactElement' object let nextChildren = nextProps. const isDirectTextChild = shouldSetTextContent(type, nextProps); If (isDirectTextChild) {if (isDirectTextChild) {if (isDirectTextChild) {if (isDirectTextChild) {if (isDirectTextChild) {if (isDirectTextChild) {if (isDirectTextChild) {if (isDirectTextChild) { } else if (prevProps ! = = null && shouldSetTextContent (type, prevProps)) {/ / operation need to set up special fiber. The effectTag workInProgress. EffectTag | = ContentReset;  } // Special operation needs to set fiber. EffectTag markRef(current, workInProgress); // 3. From the 'ReactElement' object, the reconcilerChildren is called to generate 'Fiber' child nodes, and the first child fiber node is assigned to workinProgress.child. reconcileChildren(current, workInProgress, nextChildren, renderLanes); return workInProgress.child; }Copy the code

In each updateXXX function, can judge whether the current node needs to be updated, if you don’t need to update will enter bailoutOnAlreadyFinishedWork, and results of using bailoutOnAlreadyFinishedWork as beginWork return values, BeginWork early, without entering the diff stage.

Common situations that do not require updates

  1. If when updateClassComponent! shouldUpdate && ! didCaptureError
  2. UpdateFunctionComponent if current! == null && ! didReceiveUpdate
  3. Compare (prevProps, nextProps) && current. Ref === workinprogress.ref
  4. UpdateHostRoot nextChildren === prevChildren

bailoutOnAlreadyFinishedWork

BailoutOnAlreadyFinishedWork internal will determine first! IncludesSomeLane (renderLanes, workInProgress childLanes) is established.

If! IncludesSomeLane (renderLanes, workInProgress childLanes) was established, all child nodes do not need to update, or update the priority is lower than the current update rendering priority. At this point, the whole subtree with this node as the head node can be directly reused. The entire subtree is skipped and null is used as the return value of beginWork (into the logic of backtracking);

If this is not true, it means that although the current node does not need to be updated, some fiber sub-nodes of the current node need to be updated in this rendering, so the secondary nodes of workInProgress are generated by multiplexing current Fiber.

function bailoutOnAlreadyFinishedWork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes, ): Fiber | null { //... if (! includesSomeLane(renderLanes, WorkInProgress. ChildLanes)) {/ / renderLanes does not contain workInProgress. ChildLanes / / all child nodes do not need to update in the update operation, skip, Return null; } / /... CloneChildFibers (current, workInProgress); cloneChildFibers(workInProgress); return workInProgress.child; }Copy the code

effectTag

As described above, in the main logic of updateXXX, effectTags are set based on the actual situation when the reconcilerChildren are called to generate Fiber child nodes from the ReactElement object and the ReactElement object. So what exactly does effrctTag do?

As we know, one of the aims of the Reconciler is to identify the components that are changing and then inform the Renderer of the DOM operations that need to be performed, and it is the effectTag that holds the specific types of DOM operations that need to be performed. Effecttags are represented in binary:

/ /... // means that the DOM node corresponding to the Fiber node needs to be inserted into the page. export const Placement = /* */ 0b000000000000010; // means that the Fiber node needs to be updated. export const Update = /* */ 0b000000000000100; export const PlacementAndUpdate = /* */ 0b000000000000110; // means that the DOM node corresponding to the Fiber node needs to be removed from the page. export const Deletion = /* */ 0b000000000001000; / /...Copy the code

Saving the effectTag in this way makes it easy to assign multiple effects to fiber and determine whether the current fiber has an effect using bit-manipulation.

React’s Priority Lane model also uses binary representation of priorities.

reconcileChildren

In each updateXXX function, the reconcilerChildren are called to generate the subfiber child nodes of the current workInProgress Fiber node, based on the subreactelement object that is obtained.

In double buffering we introduce:

In the coordination phase, React uses the diff algorithm to compare the Update ReactElement with the corresponding nodes in the current Fiber tree, and finally generates the workInProgress Fiber Tree in memory. The Renderer then renders the update to the page based on the workInProgress Fiber Tree. At the same time, the current attribute of the root node points to the workInProgress Fiber Tree, and the workInProgress Fiber Tree becomes the Current Fiber Tree.

The process of diff takes place in reconcileChildren.

This paper focuses on reconciling the Reconciler, and we only need to understand the purpose of the reconcileChildren function, not the implementation of the Diff algorithm in reconcileChildren. The React diff algorithm can be used to modify the React diff algorithm.

The reconcileChildren also distinguishes the mount from the Update through current === NULL, which then performs different tasks:

export function reconcileChildren( current: Fiber | null, workInProgress: Fiber, nextChildren: any, renderLanes: Lanes) {if (current === null) {workinprogress. child = mountChildProgress (workInProgress, null, nextChildren, renderLanes, ); } else {// For the components of the update workinprogress. child = reconcileChildFibers(workInProgress, current. Child, nextChildren, renderLanes, ); }}Copy the code

MountChildFibers and reconcileChildFibers are generated through ChildReconciler. The difference is that the shouldTrackSideEffects parameter is different. When shouldTrackSideEffects is true, it collects the effectTag attribute for the generated fiber node, whereas if shouldTrackSideEffects is true, it does not.

The goal is to improve the efficiency of the COMMIT phase. If mountChildFibers also assign effectTags, since mountChildFibers’ nodes are rendered for the first time, their effectTags will all collect Placement EffectTags. DOM manipulation during the COMMIT phase results in each fiber node needing to be inserted. To solve this problem, only the root node collects effectTags during mount and only one insert is performed during commit.

export const reconcileChildFibers = ChildReconciler(true); export const mountChildFibers = ChildReconciler(false); function ChildReconciler(shouldTrackSideEffects) { //... function reconcileChildrenArray( returnFiber: Fiber, currentFirstChild: Fiber | null, newChildren: Array<*>, lanes: Lanes, ): Fiber | null { //... } / /... function reconcileSingleElement( returnFiber: Fiber, currentFirstChild: Fiber | null, element: ReactElement, lanes: Lanes, ): Fiber { //... } / /... function reconcileChildFibers( returnFiber: Fiber, currentFirstChild: Fiber | null, newChild: any, lanes: Lanes, ): Fiber | null { //... } return reconcileChildFibers; }Copy the code

ChildReconciler internally defines a number of functions for manipulating the Fiber nodes, and eventually uses a function called reconcileChildFibers as its return value. The main purpose of this function is to generate the sub-fiber node of the current workInProgress Fiber node and take the first sub-fiber node as the beginWork return value.

In addition to the downward generation of child nodes, the implementation of the reconcileChildFibers takes place the following:

  1. I’m going to be incommitThe operations to be performed on the DOM node (such as add, move:PlacementDelete:Deletion) to collecteffectTag;
  2. For the deletedfiberNodes, except for their own effectTags need to be collectedDeletionIn addition, it is added to the parent node’seffectListThe normal collection of effectLists is incompleteWork, but the deleted node will detachfiberTree, inaccessiblecompleteWorkThe process, so inbeginWorkPhase is added to the parent node in advanceeffectList).

In the traversal process, we can see that when the return value of beginWork is not empty, the value will be assigned to workInProgress as the next unit of work, that is, the traversal of a node in the parent -> child linked list is completed. If beginWork returns a null value, we will enter completeWork.

completeUnitOfWork

When the beginWork return value is null, it means that the current list has no next node in the process of traversing the parent -> child list (that is, the current parent -> child list has been traversed), and the completeUnitOfWork function will be entered.

CompleteUnitOfWork does the following things:

  1. Call completeWork.

  2. For collecting effectLists of parent nodes:

    • Add the current fiber nodeeffectListMerged to the parent nodeeffectListIn the.
    • If the current fiber node has side effects (add, delete, change), add them to the parent nodeeffectListIn the.
  3. See if it has a sibling node along the sibling -> sibling list (i.e., fiber.sibling! == null), if it exists, the sibling fiber parent -> child list will be traversed (i.e. enter the beginWork stage of the sibling node). If no sibling fiber exists, the parent node is traced through the child -> parent list until the root node is traced, which completes the coordination.

function completeUnitOfWork(unitOfWork: Fiber): void { let completedWork = unitOfWork; // This loop controls the fiber node back to the parent. Do {const current = completedwork.alternate; const returnFiber = completedWork.return; if ((completedWork.flags & Incomplete) === NoFlags) { let next; CompleteWork next = completeWork(current, completedWork, subtreeRenderLanes); // Process a single node if (next! Components of Suspense type may derive other nodes, at this time go back to 'beginWork' stage to process this node workInProgress = next; return; } // Reset the priority of child nodes resetChildLanes(completedWork); if ( returnFiber ! == null && (returnFiber.flags & Incomplete) === NoFlags) {// Merge this node's effectList into the parent node's effectList if (returnFiber.firstEffect === null) { returnFiber.firstEffect = completedWork.firstEffect; } if (completedWork.lastEffect ! == null) { if (returnFiber.lastEffect ! == null) { returnFiber.lastEffect.nextEffect = completedWork.firstEffect; } returnFiber.lastEffect = completedWork.lastEffect; } // If the current fiber node has side effects (add, delete, change), add them to the parent node's effectList. const flags = completedWork.flags; if (flags > PerformedWork) { if (returnFiber.lastEffect ! == null) { returnFiber.lastEffect.nextEffect = completedWork; } else { returnFiber.firstEffect = completedWork; } returnFiber.lastEffect = completedWork; }}} else {// exception handling //... } const siblingFiber = completedWork.sibling; if (siblingFiber ! WorkInProgress = siblingFiber; workInProgress = siblingFiber; return; } // If there are no siblings, go back to the parent completedWork = returnFiber; workInProgress = completedWork; } while (completedWork ! == null); // Back to the root node, Set workInProgressRootExitStatus = RootCompleted if (workInProgressRootExitStatus = = = RootIncomplete) { workInProgressRootExitStatus = RootCompleted; }}Copy the code

completeWork

The roles of completeWork include:

  1. Generate the corresponding DOM node for the new Fiber node.

  2. Update the DOM node properties.

  3. Perform event binding.

  4. Collect effectTag.

Similar to beginWork, completeWork goes into a different logical processing branch for each fiber. Tag.

function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  const newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
    case IndeterminateComponent:
    case LazyComponent:
    case SimpleMemoComponent:
    case FunctionComponent:
    case ForwardRef:
    case Fragment:
    case Mode:
    case Profiler:
    case ContextConsumer:
    case MemoComponent:
      return null;
    case ClassComponent: {
      // ...
      return null;
    }
    case HostRoot: {
      // ...
      return null;
    }
    case HostComponent: {
      // ...
      return null;
    }
  // ...
}
Copy the code

Let’s continue our analysis using a node of type HostComponent as an example.

When dealing with the HostComponent, we also need to distinguish whether the current node needs to be created or updated. However, different from the beginWork phase, when judging whether a node needs to be updated, the current! If the workinprogress. stateNode is null, the workinprogress. stateNode is null. == null && workInProgress.stateNode ! = null, we will do the update operation.

The stateNode attribute of the node mounted in the beginWork stage is empty and will not be assigned until it enters the completeWork stage. If an update of a higher priority interrupts the update after the node enters the beginWork stage and before it enters the completeWork stage, current! If workinprogress. stateNode == null, create a new operation.

update

The stateNode attribute of the Fiber node entering the update logic is not empty, that is, the corresponding DOM node already exists. All we need to do is update the DOM node attributes and collect the associated EffectTags.

if (current !== null && workInProgress.stateNode != null) {
  updateHostComponent(
    current,
    workInProgress,
    type,
    newProps,
    rootContainerInstance,
  );

  // ref更新时,收集Ref effectTag
  if (current.ref !== workInProgress.ref) {
    markRef(workInProgress);
  }
}
Copy the code

updateHostComponent

UpdateHostComponent updates DOM node attributes and collects Update EffectTags on the current node.

updateHostComponent = function( current: Fiber, workInProgress: Fiber, type: Type, newProps: // Props, rootContainerInstance: Container,) {// There is no change, skip the current node const oldProps = current. MemoizedProps; if (oldProps === newProps) { return; } const instance: Instance = workInProgress.stateNode; const currentHostContext = getHostContext(); // Calculate the DOM node attributes that need to be changed and store them in updatePayload, which is an array of prop keys with even index values and prop values with odd index values. const updatePayload = prepareUpdate( instance, type, oldProps, newProps, rootContainerInstance, currentHostContext, ); / / will updatePayload mount to workInProgress. UpdateQueue, used for subsequent commit phase workInProgress. UpdateQueue = (updatePayload: any); Update effectTag if (updatePayload) {markUpdate(workInProgress); }};Copy the code

We can see that the prop that needs to be changed is stored in updatePayload, which is an array of the changed prop key indexed with even values and the changed Prop value indexed with odd values. And finally mount to mount to workInProgress. UpdateQueue, used for subsequent commit phase.

prepareUpdate

The diff method is called inside prepareUpdate to calculate the updatePayload.

export function prepareUpdate(
  instance: Instance,
  type: string,
  oldProps: Props,
  newProps: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
): null | Object {
  const viewConfig = instance.canonical.viewConfig;
  const updatePayload = diff(oldProps, newProps, viewConfig.validAttributes);

  instance.canonical.currentProps = newProps;
  return updatePayload;
}
Copy the code

The diff method is actually implemented internally using the diffProperties method, which compares lastProps and nextProps:

  1. For input/option/select/textarea lastProps & nextProps do special handling, and React controlled components related here, don’t do.

  2. Traverse lastProps:

    • The loop is broken (continue) when the traversed prop property also exists in the nextProps. If the prop property traversed does not exist in nextProps, go to the next step.
    • Check whether the current prop is a style prop. If not, go to the next step. If yes, add the style prop to styleUpdates, where styleUpdates are the key value of the style prop. “(empty string) is the object of value, used to empty the style property.
    • Since the prop entered in this step does not exist in nextProps, unpack the prop of this type into updatePayload and assign it to null to delete this property.
  3. Traverse nextProps:

    • If the traversal of prop property is equal to lastProp, i.e., there is no change before or after the update, skip.
    • Check whether the current prop is a style prop. If not, go to the next step. If yes, add styleUpdates to the styleUpdates variable, where styleUpdates is the key of the style prop. Tyle prop value is the value object used to update the style property.
    • Special handling DANGEROUSLY_SET_INNER_HTML
    • Special treatment children
    • If all of the above scenarios fail, simply add the prop keys and values to updatePayload.
  4. If styleUpdates are not empty, collate styleUpdates as the value of style Prop into updatePayload.

When new

The stateNode attribute of the fiber node entering the new logic is empty, and there is no corresponding DOM node. We need to do more than update operations:

  1. Generate the corresponding DOM node for the Fiber node and assign it to the stateNode attribute.

  2. Insert the descendant DOM node into the newly generated DOM node.

  3. Handles all attributes of the DOM node as well as event callbacks.

  4. Collect effectTag.

if (current ! == null && workInProgress.stateNode ! = null) {// Update operation //... } else {// Create a DOM node const instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress, ); AppendAllChildren (instance, workInProgress, false, false); // Assign the DOM node to the stateNode attribute workinprogress. stateNode = instance; If (finalizeInitialChildren(instance, type, newProps, rootContainerInstance, currentHostContext, ) ) { markUpdate(workInProgress); }}Copy the code

createInstance

CreateInstance generates the corresponding DOM node for the Fiber node.

export function createInstance( type: string, props: Props, rootContainerInstance: Container, hostContext: HostContext, internalInstanceHandle: Object, ): Instance { let parentNamespace: string; / /... // Create DOM element const domElement: Instance = createElement(type, props, rootContainerInstance, parentNamespace,); PrecacheFiberNode (internalInstanceHandle, domElement); precacheFiberNode(internalInstanceHandle, domElement); // mount a pointer to the DOM node updateFiberProps(domElement, props); return domElement; }Copy the code

appendAllChildren

AppendAllChildren is responsible for inserting the descendant DOM node into the newly generated DOM node.

appendAllChildren = function( parent: Instance, workInProgress: Fiber, needsVisibilityToggle: boolean, isHidden: Boolean,) {// Get subfiber node of workInProgress let node = workinprogress.child; // If there are child nodes, go down while (node! = = null) {if (node. Tag = = = HostComponent | | node. The tag = = = HostText) {/ / when the node node is after HostComponent HostText, AppendInitialChild (parent, node.statenode) directly into the child DOM node list; } else if (enableFundamentalAPI && node.tag === FundamentalComponent) { appendInitialChild(parent, node.stateNode.instance); } else if (node.tag === HostPortal) {// Do nothing when node.tag === HostPortal) else if (node.child! Return = node; return = node; return = node; return = stateNode; node = node.child; continue; } if (node === workInProgress) {return if (node === workInProgress) {return if (node === workInProgress) { } // Back up while (node.sibling === null) {// Back up to workInProgress, To add all the child nodes if (node. Return = = = null | | node. Return = = = workInProgress) {return; } node = node.return; } // The first child of the workInProgress node is inserted into the DOM node corresponding to the entry of the workInProgress node. Node.sibling. Return = node.return; node = node.sibling; }}; function appendInitialChild(parentInstance: Instance, child: Instance | TextInstance): void { parentInstance.appendChild(child); }Copy the code

As we introduced in the beginWork section, in order to avoid inserting each fiber node, only the root node collects effectTags when mounting, and the rest nodes do not. Since each appendAllChildren execution, we get a DOM tree with the current workInProgress as the root node. Therefore, during the COMMIT phase, we only need to insert the root node of the mount once.

finalizeInitialChildren

function finalizeInitialChildren( domElement: Instance, type: string, props: Props, rootContainerInstance: Container, hostContext: HostContext, ): Boolean {// This method will mount the DOM attribute to the DOM node and bind the event setInitialProperties(domElement, Type, props, rootContainerInstance); / / return props. The autoFocus value return shouldAutoFocusHostComponent (type, props); }Copy the code

effectList

The completeUnitOfWork function is used to collect the parent’s effectList: – Merge the current Fiber node’s effectList into the parent’s effectList. – If the current fiber node has side effects (additions, deletions, changes), add them to the parent node’s effectList.

If (returnFiber.firsteffect === null) {returnFiber.firsteffect = completedWork.firstEffect; } if (completedWork.lastEffect ! == null) { if (returnFiber.lastEffect ! == null) { returnFiber.lastEffect.nextEffect = completedWork.firstEffect; } returnFiber.lastEffect = completedWork.lastEffect; } // If the current fiber node has side effects (add, delete, change), add them to the parent node's effectList. const flags = completedWork.flags; if (flags > PerformedWork) { if (returnFiber.lastEffect ! == null) { returnFiber.lastEffect.nextEffect = completedWork; } else { returnFiber.firstEffect = completedWork; } returnFiber.lastEffect = completedWork; }Copy the code

EffectList is a one-way linked list that collects fiber nodes with effectTags. React uses fiber.firstEffect to indicate the first fiber node in the effectList mounted to the fiber node, and fiber.lastEffect to indicate the last fiber node in the effectList mounted to the fiber node.

Effectlists exist to improve the efficiency of the COMMIT phase. During the COMMIT phase, we need to find all fiber nodes with effectTags and perform effectTag actions accordingly. React collects all effectTag nodes into the effectList during the completeUnitOfWork call in order to avoid having to walk through fiber nodes with non-empty effectTags during the commit phase. You just iterate through the effectList and perform the corresponding effectTags for each node.

End of render phase

If completedWork === null, all nodes in the workInProgress Fiber Tree have completed completeWork. WorkInProgress Fiber Tree has been built, so far, all the work of render stage has been completed.

We will return to coordinate follow-up phase of the entry function performSyncWorkOnRoot (legacy mode) or performConcurrentWorkOnRoot (concurrent mode), Call commitRoot(root), where root is fiberRootNode, to start the commit phase workflow.

Explore the React source code series

React Fiber

React Diff

React source: Reconciler