RenderRoot () calls workLoop()/workLoopSync() to update loop units:

function renderRoot(){

  xxx

  xxx

  if(workInProgress ! = =null) {

      // Perform updates for each node

      if (isSync) {

        workLoopSync();

      } else {

        // Determine whether to continue calling performUnitOfWork

        workLoop();

      }

   }

  xxx

  xxx

}

Copy the code

This article explains workLoop and its internal method logic

Note: This article deals with some properties of the Fiber object. See RootFiber for details on React source code

The loop executes performUnitOfWork and assigns the value to workInProgress until the value of workInProgress is empty. The loop is terminated

Source:

// Synchronize the workLoop, indicating that it cannot be interrupted

function workLoopSync({

  // Already timed out, so perform work without checking if we need to yield.

  while(workInProgress ! = =null) {

    workInProgress = performUnitOfWork(workInProgress);

  }

}



// Asynchronous workLoop, which can be broken

// Determine whether to continue calling performUnitOfWork

function workLoop({

  // Perform work until Scheduler asks us to yield

  / * nextUnitOfWork = "workInProgress * /

  // The root node is not reached



  // With workinprogress. child, loop until all nodes are updated

  while(workInProgress ! = =null && !shouldYield()) {

    workInProgress = performUnitOfWork(workInProgress);

  }

}

Copy the code

WorkInProgress is a Fiber object, and a non-null value means that there are still updates to be performed on the Fiber object

2, performUnitOfWork function: call beginWork, from parent to child, component (node) update; Call completeUnitOfWork, child to parent, and do some processing on the node based on the effectTag

Source:

// Navigate through and manipulate nodes from top to bottom, and then operate nodes from bottom to top according to effectTag

UnitOfWork (workInProgress) is a fiber object

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.

  //current <=> workInProgress

  // Get the current node

  const current = unitOfWork.alternate;

  // Make a mark on unitOfWork

  startWorkTimer(unitOfWork);

  //dev environment

  setCurrentDebugFiberInDEV(unitOfWork);



  let next;

  if(enableProfilerTimer && (unitOfWork.mode & ProfileMode) ! == NoMode) {

    startProfilerTimer(unitOfWork);

    // Perform node operations and create child nodes

    //current: workInProgress.alternate

    //unitOfWork: workInProgress



    //workInProgress.child

    // Check whether fiber is updated. If fiber is updated, update corresponding components. If fiber is not updated, replicate nodes

    next = beginWork(current, unitOfWork, renderExpirationTime);

    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);

  } else {

    next = beginWork(current, unitOfWork, renderExpirationTime);

  }

  / / to watch

  resetCurrentDebugFiberInDEV();

  // Replace the props to be updated with the props being used

  unitOfWork.memoizedProps = unitOfWork.pendingProps;

  // Indicates that the lowest leaf node has been updated, and the sibling of the leaf node has been traversed

  if (next === null) {

    // If this doesn't spawn new work, complete the current work.

    // After traversing from top to bottom, the completeUnitOfWork does some processing from bottom to top according to the effact tag

    next = completeUnitOfWork(unitOfWork);

  }



  ReactCurrentOwner.current = null;

  return next;

}

Copy the code

Resolution: (2) Execute beginWork(Current, unitOfWork, renderExpirationTime) to update components. Assign the value returned to next (3). Replace the props to be updated on unitOfWork with the props to be used (4). If next is null, completeUnitOfWork is executed, traversing from bottom to top, (5) Finally return to Next (next is the Fiber object)

Look at the beginWork (). CompleteUnitOfWork () will be parsed in a later article.

BeginWork notice: switch… Case… Skip the two long paragraphs

Function: Check whether fiber is updated. If fiber is updated, the corresponding components will be updated. If fiber is not updated, the nodes will be copied

Source:

// Check whether fiber is updated. If fiber is updated, update corresponding components. If fiber is not updated, replicate nodes

//current: workInProgress.alternate

function beginWork(

  current: Fiber | null,

  // The child node created by workInProgress is also workInProgress

  workInProgress: Fiber,

  // Marks the highest priority point in the render

  renderExpirationTime: ExpirationTime,

): Fiber | null {

  // Only when react.domRender is called does the expirationTime of rootFiber have a value and rootFiber is updated



  // Get the expiration time of updates on the Fiber object

  const updateExpirationTime = workInProgress.expirationTime;





  // Determine if it is the first render

  // If not the first render

  if(current ! == null) {

    // Props after the last rendering, i.e. OldProps

    const oldProps = current.memoizedProps;

    // The new changes bring props, that is, newProps

    const newProps = workInProgress.pendingProps;



    if (

      // Whether the front and back props are not equal

oldProps ! == newProps ||

      // Whether an older context is used and has changed

      hasLegacyContextChanged() ||

      // Force a re-render if the implementation changed due to hot reload:

      // Development environment is always false

      (__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).

      // Determine that an update is received

      didReceiveUpdate = true;

    }

    // There is an update, but the priority is not high, it does not need to be executed during this rendering, set to false

    else if (updateExpirationTime < renderExpirationTime) {

      didReceiveUpdate = false;

      // This fiber does not have any pending work. Bailout without entering

      // the begin phase. There's still some bookkeeping we that needs to be done

      // in this optimized path, mostly pushing stuff onto the stack.

      // Update the corresponding component according to the workInProgress tag

      switch (workInProgress.tag) {

        case HostRoot:

          pushHostRootContext(workInProgress);

          resetHydrationState();

          break;

        case HostComponent:

          pushHostContext(workInProgress);

          if (

            workInProgress.mode & ConcurrentMode &&

renderExpirationTime ! == Never &&

            shouldDeprioritizeSubtree(workInProgress.type, newProps)

          ) {

            if (enableSchedulerTracing) {

              markSpawnedWork(Never);

            }

            // Schedule this fiber to re-render at offscreen priority. Then bailout.

            workInProgress.expirationTime = workInProgress.childExpirationTime = Never;

            return null;

          }

          break;

        case ClassComponent: {

          const Component = workInProgress.type;

          if (isLegacyContextProvider(Component)) {

            pushLegacyContextProvider(workInProgress);

          }

          break;

        }

        case HostPortal:

          pushHostContainer(

            workInProgress,

            workInProgress.stateNode.containerInfo,

          );

          break;

        case ContextProvider: {

          const newValue = workInProgress.memoizedProps.value;

          pushProvider(workInProgress, newValue);

          break;

        }

        case Profiler:

          if (enableProfilerTimer) {

            workInProgress.effectTag |= Update;

          }

          break;

        case SuspenseComponent: {

          const state: SuspenseState | null = workInProgress.memoizedState;

          constdidTimeout = state ! == null;

          if (didTimeout) {

            // If this boundary is currently timed out, we need to decide

            // whether to retry the primary children, or to skip over it and

            // go straight to the fallback. Check the priority of the primary

            // child fragment.

            const primaryChildFragment: Fiber = (workInProgress.child: any);

            const primaryChildExpirationTime =

              primaryChildFragment.childExpirationTime;

            if (

primaryChildExpirationTime ! == NoWork &&

              primaryChildExpirationTime >= renderExpirationTime

            ) {

              // The primary children have pending work. Use the normal path

              // to attempt to render the primary children again.

              return updateSuspenseComponent(

                current,

                workInProgress,

                renderExpirationTime,

              );

            } else {

              pushSuspenseContext(

                workInProgress,

                setDefaultShallowSuspenseContext(suspenseStackCursor.current),

              );

              // The primary children do not have pending work with sufficient

              // priority. Bailout.

              const child = bailoutOnAlreadyFinishedWork(

                current,

                workInProgress,

                renderExpirationTime,

              );

              if(child ! == null) {

                // The fallback children have pending work. Skip over the

                // primary children and work on the fallback.

                return child.sibling;

              } else {

                return null;

              }

            }

          } else {

            pushSuspenseContext(

              workInProgress,

              setDefaultShallowSuspenseContext(suspenseStackCursor.current),

            );

          }

          break;

        }

        case DehydratedSuspenseComponent: {

          if (enableSuspenseServerRenderer) {

            pushSuspenseContext(

              workInProgress,

              setDefaultShallowSuspenseContext(suspenseStackCursor.current),

            );

            // We know that this component will suspend again because if it has

            // been unsuspended it has committed as a regular Suspense component.

            // If it needs to be retried, it should have work scheduled on it.

            workInProgress.effectTag |= DidCapture;

          }

          break;

        }

        case SuspenseListComponent: {

          const didSuspendBefore =

(current.effectTag & DidCapture) ! == NoEffect;



          const childExpirationTime = workInProgress.childExpirationTime;

          if (childExpirationTime < renderExpirationTime) {

            // If none of the children had any work, that means that none of

            // them got retried so they'll still be blocked in the same way

            // as before. We can fast bail out.

            pushSuspenseContext(workInProgress, suspenseStackCursor.current);

            if (didSuspendBefore) {

              workInProgress.effectTag |= DidCapture;

            }

            return null;

          }



          if (didSuspendBefore) {

            // If something was in fallback state last time, and we have all the

            // same children then we're still in progressive loading state.

            // Something might get unblocked by state updates or retries in the

            // tree which will affect the tail. So we need to use the normal

            // path to compute the correct tail.

            return updateSuspenseListComponent(

              current,

              workInProgress,

              renderExpirationTime,

            );

          }



          // If nothing suspended before and we're rendering the same children,

          // then the tail doesn't matter. Anything new that suspends will work

          // in the "together" mode, so we can continue from the state we had.

          let renderState = workInProgress.memoizedState;

          if(renderState ! == null) {

            // Reset to the "together" mode in case we've started a different

            // update in the past but didn't complete it.

            renderState.rendering = null;

            renderState.tail = null;

          }

          pushSuspenseContext(workInProgress, suspenseStackCursor.current);

          break;

        }

        case EventComponent:

          if (enableFlareAPI) {

            pushHostContextForEventComponent(workInProgress);

          }

          break;

      }

      // Skip the update of this node and all its children

      return bailoutOnAlreadyFinishedWork(

        current,

        workInProgress,

        renderExpirationTime,

      );

    }

  } else {

    didReceiveUpdate = false;

  }



  // Before entering the begin phase, clear the expiration time.

  workInProgress.expirationTime = NoWork;

  // If the node is updated

  // Update components based on node type

  switch (workInProgress.tag) {

    case IndeterminateComponent: {

      return mountIndeterminateComponent(

        current,

        workInProgress,

        workInProgress.type.

        renderExpirationTime,

      );

    }

    case LazyComponent: {

      const elementType = workInProgress.elementType;

      return mountLazyComponent(

        current,

        workInProgress,

        elementType,

        updateExpirationTime,

        renderExpirationTime,

      );

    }

    / / FunctionComponent updates

    case FunctionComponent: {

      const Component = workInProgress.type;

      const unresolvedProps = workInProgress.pendingProps;

      const resolvedProps =

        workInProgress.elementType === Component

          ? unresolvedProps

          : resolveDefaultProps(Component, unresolvedProps);

      return updateFunctionComponent(

        current,

        workInProgress,

        Component,

        resolvedProps,

        renderExpirationTime,

      );

    }

    / / ClassComponent updates

    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,

      );

    }

    case HostRoot:

      return updateHostRoot(current, workInProgress, renderExpirationTime);

    case HostComponent:

      return updateHostComponent(current, workInProgress, renderExpirationTime);

    case HostText:

      return updateHostText(current, workInProgress);

    case SuspenseComponent:

      return updateSuspenseComponent(

        current,

        workInProgress,

        renderExpirationTime,

      );

    case HostPortal:

      return updatePortalComponent(

        current,

        workInProgress,

        renderExpirationTime,

      );

    case ForwardRef: {

      const type = workInProgress.type;

      const unresolvedProps = workInProgress.pendingProps;

      const resolvedProps =

        workInProgress.elementType === type

          ? unresolvedProps

          : resolveDefaultProps(type, unresolvedProps);

      return updateForwardRef(

        current,

        workInProgress,

        type.

        resolvedProps,

        renderExpirationTime,

      );

    }

    case Fragment:

      return updateFragment(current, workInProgress, renderExpirationTime);

    case Mode:

      return updateMode(current, workInProgress, renderExpirationTime);

    case Profiler:

      return updateProfiler(current, workInProgress, renderExpirationTime);

    case ContextProvider:

      return updateContextProvider(

        current,

        workInProgress,

        renderExpirationTime,

      );

    case ContextConsumer:

      return updateContextConsumer(

        current,

        workInProgress,

        renderExpirationTime,

      );

    case MemoComponent: {

      const type = workInProgress.type;

      const unresolvedProps = workInProgress.pendingProps;

      // Resolve outer props first, then resolve inner props.

      let resolvedProps = resolveDefaultProps(type, unresolvedProps);

      if (__DEV__) {

        if (workInProgress.type! == workInProgress.elementType) {

          const outerPropTypes = type.propTypes;

          if (outerPropTypes) {

            checkPropTypes(

              outerPropTypes,

              resolvedProps, // Resolved for outer only

              'prop'.

              getComponentName(type),

              getCurrentFiberStackInDev,

            );

          }

        }

      }

      resolvedProps = resolveDefaultProps(type.type, resolvedProps);

      return updateMemoComponent(

        current,

        workInProgress,

        type.

        resolvedProps,

        updateExpirationTime,

        renderExpirationTime,

      );

    }

    case SimpleMemoComponent: {

      return updateSimpleMemoComponent(

        current,

        workInProgress,

        workInProgress.type.

        workInProgress.pendingProps,

        updateExpirationTime,

        renderExpirationTime,

      );

    }

    case IncompleteClassComponent: {

      const Component = workInProgress.type;

      const unresolvedProps = workInProgress.pendingProps;

      const resolvedProps =

        workInProgress.elementType === Component

          ? unresolvedProps

          : resolveDefaultProps(Component, unresolvedProps);

      return mountIncompleteClassComponent(

        current,

        workInProgress,

        Component,

        resolvedProps,

        renderExpirationTime,

      );

    }

    case DehydratedSuspenseComponent: {

      if (enableSuspenseServerRenderer) {

        return updateDehydratedSuspenseComponent(

          current,

          workInProgress,

          renderExpirationTime,

        );

      }

      break;

    }

    case SuspenseListComponent: {

      return updateSuspenseListComponent(

        current,

        workInProgress,

        renderExpirationTime,

      );

    }

    case EventComponent: {

      if (enableFlareAPI) {

        return updateEventComponent(

          current,

          workInProgress,

          renderExpirationTime,

        );

      }

      break;

    }

  }

  invariant(

    false.

    'Unknown unit of work tag. This error is likely caused by a bug in ' +

      'React. Please file an issue.'.

  );

}

Copy the code

MemoizedProps and pendingProps are my expirationTime memoizedProps and my memoizedProps are my expirationTime memoizedProps. React RootFiber (3) switch… Context updateExpirationTime < renderExpirationTime = context It means that the current fiber update priority is not high, in the process of the rendering will not perform its update, so will perform bailoutOnAlreadyFinishedWork, the renewal of the node and all child nodes to skip, The workinprogress. tag is used to update the workinprogress. tag

Four, bailoutOnAlreadyFinishedWork role: skip all child nodes on the node and the node updates

Source:

// Skip the update of this node and all its children

function bailoutOnAlreadyFinishedWork(

  current: Fiber | null,

  workInProgress: Fiber,

  renderExpirationTime: ExpirationTime,

)
Fiber | null 
{

  / / to watch

  cancelWorkTimer(workInProgress);



  if(current ! = =null) {

    // Reuse previous dependencies

    workInProgress.dependencies = current.dependencies;

  }



  if (enableProfilerTimer) {

    // Don't update "base" render times for bailouts.

    stopProfilerTimerIfRunning(workInProgress);

  }



  // Check if the children have any pending work.

  //expirationTime indicates whether this node has updates. If this node has updates, it may affect the updates of child nodes

  // If both expirationTime and childExpirationTime are not present, then the subtree does not need to be updated



  // Updates due to descendant nodes

  const childExpirationTime = workInProgress.childExpirationTime;

  If the subtree does not need to be updated, null is returned



  // One of the benefits of childExpirationTime is that it is quick to know if a subtree is up to date, so that unupdated subtrees can be skipped

  // If childExpirationTime is empty, React also needs to traverse the subtree to see if it is updated

  if (childExpirationTime < renderExpirationTime) {

    // The children don't have any work either. We can skip them.

    // TODO: Once we add back resuming, we should check if the children are

    // a work-in-progress set. If so, we need to transfer their effects.



    // Skip the update rendering of the entire subtree, which is a very big optimization

    return null;

  }

  // Harmonize child nodes

  else {

    // This fiber doesn't have work, but its subtree does. Clone the child

    // fibers and continue.

    // This node does not need to be updated, nor does the child node, so just copy the child node over

    cloneChildFibers(current, workInProgress);

    return workInProgress.child;

  }

}

Copy the code

Resolution: Normally, child updates are evaluated by traversal of the child tree, but React cleverly sets the childExpirationTime when the child updates, and eventually sets the childExpirationTime with the highest priority on the parent node. If the childExpirationTime priority is less than renderExpirationTime, the subtree traversal and update rendering are skipped.

This is a very big optimization.

CompleteUnitOfWork has a similar structure to beginWork, but I will cover it in a later article

WorkLoop Flow chart

Seven, making github.com/AttackXiaoJ…


(after)