React Source Code

    6. Functional components-construction details and updates (Part 1)

    Published
    November 20, 2022
    Reading Time
    3 min read
    Author
    Felix
    Access
    Public

    In the previous process, we have already understood Fiber, which can help us understand optimization-level scheduling mechanisms and other optimization application performance. Then we will further deepen the optimization of React through Hooks and functional components.

    Processing flow of functional components

    beginWork stage

    We start directly from beginWork and explain the update and construction of the most commonly used functional components. Source code address

    switch (workInProgress.tag) {
        // ignore code
        // Construction of child nodes of functional components
        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,
          );
        }
        // ignore code

    In the beginWork phase, React will select the corresponding processing method based on the component type. For functional components, it will call the updateFunctionComponent method.

    Update analysis

    The main task of the beginWork exploration phase is to set whether the status needs to be updated. This state is the basis for many subsequent code judgments.

    if (current !== null) {
      //Double cache mechanism
      const oldProps = current.memoizedProps
      const newProps = workInProgress.pendingProps
      // If not equal, enter comparison
      if (oldProps !== newProps || hasLegacyContextChanged() || (__DEV__ ? workInProgress.type !== current.type : false)) {
        // The status mark needs to update this status. Remember that many subsequent tasks related to this status will be related to it.
        didReceiveUpdate = true
        // If the rendering lane does not contain updates, there is no need to update
      } else if (!includesSomeLane(renderLanes, updateLanes)) {
        didReceiveUpdate = false
        //Context special processing, optimization
        switch (workInProgress.tag) {
        }
        // Does the loop child node need to be updated?
        return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes)
      } else {
        if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
          //To handle the problem that the Suspense component does not update when the Context is updated, force the mark to become true.
          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 {
      // The rest does not need to be updated
      didReceiveUpdate = false
    }

    This code mainly does the following things:

    1. Compare whether the old and new props are equal
    2. Check whether the context has changed
    3. Set the didReceiveUpdate status according to the comparison result
    4. Handle special situations, such as forced updates of Suspense components

    updateFunctionComponent

    After setting the update state, React will call the updateFunctionComponent method to handle the functional component.

    function updateFunctionComponent(
      current,
      workInProgress,
      Component,
      nextProps: any,
      renderLanes,
    ) {
      // ignore code
    
      let context;
      let nextChildren;
      // This is related to useContext. Let’s talk about it in the hooks branch.
      prepareToReadContext(workInProgress, renderLanes);
      if (__DEV__) {
        //Ignore code
      } else {
        //Here we directly enter the hooks related logic, and finally return the lower-level ReactElement object.
        nextChildren = renderWithHooks(
          current,
          workInProgress,
          Component,
          nextProps,
          context,
          renderLanes,
        );
      }
      //Old rule, if the status of didReceiveUpdate is that it does not need to be updated as mentioned earlier, search down the subsection.
      if (current !== null && !didReceiveUpdate) {
        bailoutHooks(current, workInProgress, renderLanes);
        return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
      }
    
      // React DevTools reads this flag. We will skip the performance stuff for now
      workInProgress.flags |= PerformedWork;
      // Generate child fiber nodes downwards
      reconcileChildren(current, workInProgress, nextChildren, renderLanes);
      return workInProgress.child;
    }

    The main work of updateFunctionComponent includes:

    1. Prepare context
    2. Call renderWithHooks to execute the function component
    3. Determine whether an update is required based on the status of didReceiveUpdate
    4. Generate sub-Fiber nodes

    renderWithHooks

    The renderWithHooks function is the core of the Hooks mechanism, which provides state management and side effect processing capabilities for functional components:

    export function renderWithHooks<Props, SecondArg>(
      current: Fiber | null,
      workInProgress: Fiber,
      Component: (p: Props, arg: SecondArg) => any,
      props: Props,
      secondArg: SecondArg,
      nextRenderLanes: Lanes,
    ): any {
      // Phase 1: Set the global status
      renderLanes = nextRenderLanes; // Current rendering priority
      currentlyRenderingFiber = workInProgress; //The current fiber node, which is the fiber node corresponding to the function component
      if (__DEV__) {
        //Ignore code
      }
      // Clear the legacy status of the current fiber
      workInProgress.memoizedState = null;
      workInProgress.updateQueue = null;
      workInProgress.lanes = NoLanes;
       if (__DEV__) {
        //Ignore code
       } else {
          // Phase 2: Generate ReactElement child nodes
          // ReactCurrentDispatcher comes from the global variable in react-dom, confirm whether it is initialized or updated
          ReactCurrentDispatcher.current =
            current === null || current.memoizedState === null
              ?HooksDispatcherOnMount
              : HooksDispatcherOnUpdate;
          //Execute function function
          let children = Component(props, secondArg);
       }
    
      //Reset global variables and return
      // After executing the function, restore the modified global variables without affecting the next call.
      renderLanes = NoLanes;
      currentlyRenderingFiber = (null: any);
    
      currentHook = null;
      workInProgressHook = null;
      didScheduleRenderPhaseUpdate = false;
    
      return children;
    }

    The workflow of this function can be divided into three stages:

    1. Preparation phase: Set global variables and clear old status.
    2. Execution phase: Depending on whether it is the first rendering or update, select the appropriate Hooks processor, and then execute the component function.
    3. Cleanup phase: Reset global variables to prevent affecting other components.

    Execution process analysis

    Try online

    Through breakpoint analysis, we can understand the execution process of function components:

    1. Initialization phase:

      • When the component first enters beginWork, it is marked as indeterminateComponent (indeterminate component type)
      • Execute renderWithHooks
      • After execution, mark FunctionComponent
    2. Update phase:

      • Directly enter the updateFunctionComponent logic ![Execution process diagram 1](https://ik.imagekit.io/leiakito/Vue3+ELLnput/React%20%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%20%E6 %94%AF%E7%BA%BF%20%E7%BB%84%E4%BB%B6%20%E6%9E%84%E5%BB%BA%E7%BB%86%E8%8A%82.webp?updatedAt=1739720712530) ![Execution Process Figure 2](https://ik.imagekit.io/leiakito/Vue3+ELLnput/923c00657040409ba6031935d9 13a946~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.webp?updatedAt=1739720711840)

    Summary

    This article details the construction and update process of React functional components, from beginWork to updateFunctionComponent, and then to renderWithHooks. We learned how React handles functional components, how to decide whether to update, and the core implementation of the Hooks mechanism.

    The next article will delve into the implementation of specific Hooks in function components and how to optimize performance.

    Comments

    Join the conversation

    0 comments
    Sign in to comment

    No comments yet. Be the first to add one.