react source code analysis - Introduction to beginWork

Posted by MathewByrne on Sun, 31 Oct 2021 10:53:57 +0100

catalogue

2021SC@SDUSC

Execution process of beginWork

Talk about the updatesclasscomponent method

About bailoutonalreadyfinished work

Entrance to DIFF algorithm

summary

2021SC@SDUSC

Execution process of beginWork

Talking about beginWork, what react needs to schedule is the construction and update of the Fiber tree, so we start with beginWork, which is introduced from ReactFiberBeginWork.new.js. In beginWork, we will compare the nodes in the current tree with the nodes in the workinprogress tree to judge whether it needs to be updated or mounted, If the child node needs to be mounted or cannot be updated, mark the current child node and return the new child node. Otherwise, execute bailoutonalreadyfinished work multiplexing. If the function returns the child node (indicating that the child node needs to process the offspring, judge with childlanes), continue to process the child node. If the return value is null, That means that the processing has been completed (it seems that the traversal of the Fiber tree is a deep traversal, all the way to the child nodes. The current tree is the rendered tree, and the workinprogress tree is the newly constructed tree).

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  if (__DEV__) {
    if (workInProgress._debugNeedsRemount && current !== null) {
      // This will restart the begin phase with a new fiber.
      return remountFiber(
        current,
        workInProgress,
        createFiberFromTypeAndProps(
          workInProgress.type,
          workInProgress.key,
          workInProgress.pendingProps,
          workInProgress._debugOwner || null,
          workInProgress.mode,
          workInProgress.lanes,
        ),
      );
    }
  }

  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 {
      // Neither props nor legacy context changes. Check if there's a pending
      // update or context change.
      const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
        current,
        renderLanes,
      );
      if (
        !hasScheduledUpdateOrContext &&
        // If this is the second pass of an error or suspense boundary, there
        // may not be work scheduled on `current`, so we check for this flag.
        (workInProgress.flags & DidCapture) === NoFlags
      ) {
        // No pending updates or context. Bail out now.
        didReceiveUpdate = false;
        return attemptEarlyBailoutIfNoScheduledUpdate(
          current,
          workInProgress,
          renderLanes,
        );
      }
      if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
        // This is a special case that only exists for legacy mode.
        // See https://github.com/facebook/react/pull/19216.
        didReceiveUpdate = true;
      } else {
        // An update was scheduled on this fiber, but there are no new props
        // nor legacy context. Set this to false. If an update queue or context
        // consumer produces a changed value, it will set this to true. Otherwise,
        // the component will assume the children have not changed and bail out.
        didReceiveUpdate = false;
      }
    }
  } else {
    didReceiveUpdate = false;
  }

  // Before entering the begin phase, clear pending update priority.
  // TODO: This assumes that we're about to evaluate the component and process
  // the update queue. However, there's an exception: SimpleMemoComponent
  // sometimes bails out later in the begin phase. This indicates that we should
  // move this assignment out of the common path and into each branch.
  workInProgress.lanes = NoLanes;

  switch (workInProgress.tag) {
    case IndeterminateComponent: {
      return mountIndeterminateComponent(
        current,
        workInProgress,
        workInProgress.type,
        renderLanes,
      );
    }
    case LazyComponent: {
      const elementType = workInProgress.elementType;
      return mountLazyComponent(
        current,
        workInProgress,
        elementType,
        renderLanes,
      );
    }
    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,
        renderLanes,
      );
    }
    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,
        renderLanes,
      );
    }
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);
    case HostText:
      return updateHostText(current, workInProgress);
    case SuspenseComponent:
      return updateSuspenseComponent(current, workInProgress, renderLanes);
    case HostPortal:
      return updatePortalComponent(current, workInProgress, renderLanes);
    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,
        renderLanes,
      );
    }
    case Fragment:
      return updateFragment(current, workInProgress, renderLanes);
    case Mode:
      return updateMode(current, workInProgress, renderLanes);
    case Profiler:
      return updateProfiler(current, workInProgress, renderLanes);
    case ContextProvider:
      return updateContextProvider(current, workInProgress, renderLanes);
    case ContextConsumer:
      return updateContextConsumer(current, workInProgress, renderLanes);
    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',
              getComponentNameFromType(type),
            );
          }
        }
      }
      resolvedProps = resolveDefaultProps(type.type, resolvedProps);
      return updateMemoComponent(
        current,
        workInProgress,
        type,
        resolvedProps,
        renderLanes,
      );
    }
    case SimpleMemoComponent: {
      return updateSimpleMemoComponent(
        current,
        workInProgress,
        workInProgress.type,
        workInProgress.pendingProps,
        renderLanes,
      );
    }
    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,
        renderLanes,
      );
    }
    case SuspenseListComponent: {
      return updateSuspenseListComponent(current, workInProgress, renderLanes);
    }
    case ScopeComponent: {
      if (enableScopeAPI) {
        return updateScopeComponent(current, workInProgress, renderLanes);
      }
      break;
    }
    case OffscreenComponent: {
      return updateOffscreenComponent(current, workInProgress, renderLanes);
    }
    case LegacyHiddenComponent: {
      return updateLegacyHiddenComponent(current, workInProgress, renderLanes);
    }
    case CacheComponent: {
      if (enableCache) {
        return updateCacheComponent(current, workInProgress, renderLanes);
      }
      break;
    }
  }
  invariant(
    false,
    'Unknown unit of work tag (%s). This error is likely caused by a bug in ' +
      'React. Please file an issue.',
    workInProgress.tag,
  );
}

Therefore, the main task of beginWork method is to judge whether the current node needs to be updated and mark it. Of course, if it is a mounted node or a node that is updated but cannot be reused, it will return to the new node. The multiplexing node needs to judge whether there are any nodes in the child nodes to be processed. If there are, it will return to the child nodes, and if not, the processing will be completed.

Talk about the updatesclasscomponent method

First, the context will be processed. Call the isLegacyContextProvider method to check whether there is a context in the component. If so, call the pushLegacyContextProvider method to make it a contextProvider. Then, update the instance variable according to the stateNode in workInProgress, which is divided into several cases. If the instance is not null and the current is not null, you can directly call the updateClassInstance method to update the instance. In updateClassInstance, you mainly reuse the instance and update props and state, And call the life cycle function, which mainly includes componentWillUpdate, componentDidUpdate and getDerivedStateFromProps. (incidentally, in the new version of react, componentWillUpdate is no longer a life cycle function, and getDerivedStateFromProps is used uniformly). In the process of updating props and state, First, process the relevant lifecycle functions, such as getDerivedStateFromProps, which means to obtain the derived state from props, and then assign the updated props and state to instance. Of course, shouldUpdate will also be returned to indicate whether the instance needs to be updated.

If instance is null, when current is not null, the flags in workInProgress should be changed to Placement, because workInProgress is a new Fiber node and should be inserted during the construction of Fiber tree in the future. Then mount the instance in the classComponent according to the value of current. At this time, you need to call mountClassInstance to mount the instance. First, the update queue will be updated and processed as an update event. Next, the context and state parts in workInProgress will be read and assigned to the instance variable. In addition, the lifecycle functions to be processed at this time are getDerivedStateFromProps, componentDidMount and other lifecycle functions. If instance is not null and current is null, you can call resumeMountClassInstance to reuse the mounted instance. In resumeMountClassInstance, it mainly includes calling life cycle functions such as componentDidMount. Of course, it is also necessary to judge the updates of props and state in the instance. In addition, it is also necessary to return shouldUpdate.

Then provide nextUnitOfWork according to the finishClassComponent method, that is, call the next node. In finishClassComponent, first update the Refs of the currently entered classComponent, and then handle the error according to whether there is DidCapture in workInPrgress. Whether this node is a node that needs to be reprocessed during processing is directly related to the selection of the next node. I will explain this further in the error boundary mechanism in the future. In addition, if there is no need to update and no error occurs, it will detect whether the component contains context, identify whether it can become a context provider, and then execute bailoutonalreadyfinished work. If it needs to be updated, coordinate its child nodes first and call the reconcileChildren method, At this time, you should assign the memorizedstate of the workInProgress node to the state of the instance variable mentioned above, and judge again whether it can become a context provider. Because reconcileChildren has adjusted the structure of the Fiber tree, you can return the child node in workInProgress as the next unitofwork, that is, the next node to be processed.

This is the general function and process of the updateclassiscomponent method in beginWork.

About bailoutonalreadyfinished work

It will judge the Lane of the child node. If it is not within the target interval, it will return a null value indicating that the update is completed. If you want to continue processing in the interval, call cloneChildFibers to create a new child node according to the workInProgress child node (in this case, because the node has not been updated, that is, the child node in current) and return.

Entrance to DIFF algorithm

Next, take the updatecclasscomponent in update as an example. If it is determined that workInProgress is class, execute reconcileChildren (which is updated with DIFF algorithm) through finishClassComponent in updatecclasscomponent. This is the entrance of DIFF algorithm. I will explain the DIFF algorithm in the next blog.

summary

Here, we introduce the function of beginWork and sort out the general process from beginWork to DIFF algorithm, and also introduce the specific logic and function of updatecclasscomponent and the function of bailoutonalreadyfinished work function. Through beginWork, the Fiber node in the workinprogress tree has completed part of the update process. During the processing of the node's update queue (I will supplement the introduction to the update queue and update tasks later), the node gets a new status. The structure of workinprogress node tree is updated through DIFF.

Topics: Front-end React