Workinprogress. tag: FunctionComponent workinprogress. tag: FunctionComponent workinprogress. tag: FunctionComponent workinprogress. tag: FunctionComponent workinprogress. tag: FunctionComponent workinprogress. tag: FunctionComponent workinprogress. tag: FunctionComponent workinprogress. tag: FunctionComponent workinprogress. tag

    / / FunctionComponent updates

    case FunctionComponent: {

      //React component type, FunctionComponent type is function, ClassComponent type is class

      const Component = workInProgress.type;

      // Next render the props to be updated

      const unresolvedProps = workInProgress.pendingProps;

      // pendingProps

      const resolvedProps =

        workInProgress.elementType === Component

          ? unresolvedProps

          : resolveDefaultProps(Component, unresolvedProps);

      / / update the FunctionComponent

      // You can see that most of these are attributes of workInProgress

      // The variable is defined to "freeze" the properties of workInProgress in function

      return updateFunctionComponent(

        //workInProgress.alternate

        current,

        workInProgress,

        //workInProgress.type

        Component,

        //workInProgress.pendingProps

        resolvedProps,

        renderExpirationTime,

      );

    }

Copy the code

How does the FunctionComponent update

UpdateFunctionComponent Performs FunctionComponent updates

Source:

/ / update the functionComponent

/ / current: workInProgress. Alternate

/ / Component: workInProgress. Type

/ / resolvedProps: workInProgress pendingProps

function updateFunctionComponent(

  current,

  workInProgress,

  Component,

  nextProps: any,

  renderExpirationTime,

{

  // Delete dev code

  // The context will be explained later

  const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);

  const context = getMaskedContext(workInProgress, unmaskedContext);



  let nextChildren;

  // Do not read the update tag

  prepareToReadContext(workInProgress, renderExpirationTime);

  prepareToReadEventComponents(workInProgress);

  // Delete dev code



  // Perform some operations on the hook functions used in the rendering process

    nextChildren = renderWithHooks(

      current,

      workInProgress,

      Component,

      nextProps,

      context,

      renderExpirationTime,

    );



  // If it is not the first render and no updates are received

  //didReceiveUpdate: optimizations on updates

  if(current ! = =null && !didReceiveUpdate) {

    bailoutHooks(current, workInProgress, renderExpirationTime);

    return bailoutOnAlreadyFinishedWork(

      current,

      workInProgress,

      renderExpirationTime,

    );

  }



  // React DevTools reads this flag.

  // Indicates that the current component was updated during rendering

  workInProgress.effectTag |= PerformedWork;

  // Change the ReactElement into a fiber object and update it to generate the corresponding DOM instance and mount it to the real DOM node

  reconcileChildren(

    current,

    workInProgress,

    nextChildren,

    renderExpirationTime,

  );

  return workInProgress.child;

}

Copy the code

Most of the parameters passed to the updateFunctionComponent are properties of the Fiber object workInProgress

Why not just pass in a workInProgress object? My own guess is to “freeze” these properties outside to prevent them from being modified in updateFunctionComponent()

In the updateFunctionComponent(), two functions are executed: renderWithHooks() and reconcileChildren().

After executing these two methods, you finally return the workinProgress.Child, which is the first child of the Fiber object being updated

(3) bailoutOnAlreadyFinishedWork workLoop of parse () in the React source code has been parsed, its role is to skip all child nodes on the node and the node updates

BailoutHooks () ¶ bailoutHooks() ¶

// Skip the hooks update

export function bailoutHooks(

  current: Fiber,

  workInProgress: Fiber,

  expirationTime: ExpirationTime,

{

  workInProgress.updateQueue = current.updateQueue;

  workInProgress.effectTag &= ~(PassiveEffect | UpdateEffect);

  // set NoWork to no update

  if (current.expirationTime <= expirationTime) {

    current.expirationTime = NoWork;

  }

}

Copy the code

Do something about the hooks function used in rendering

Source:

// Perform some operations on the hook functions used in the rendering process

export function renderWithHooks(

  current: Fiber | null,

  workInProgress: Fiber,

  Component: any,

  props: any,

  refOrContext: any,

  nextRenderExpirationTime: ExpirationTime,

) :any 
{

  renderExpirationTime = nextRenderExpirationTime;

  // The fiber object currently being rendered

  currentlyRenderingFiber = workInProgress;

  // State of the first time

nextCurrentHook = current ! = =null ? current.memoizedState : null;

  // Delete dev code



  // The following should have already been reset

  // currentHook = null;

  // workInProgressHook = null;



  // remainingExpirationTime = NoWork;

  // componentUpdateQueue = null;



  // didScheduleRenderPhaseUpdate = false;

  // renderPhaseUpdates = null;

  // numberOfReRenders = 0;

  // sideEffectTag = 0;



  // TODO Warn if no hooks are used at all during mount, then some are used during update.

  // Currently we will identify the update render as a mount because nextCurrentHook === null.

  // This is tricky because it's valid for certain types of components (e.g. React.lazy)



  // Using nextCurrentHook to differentiate between mount/update only works if at least one stateful hook is used.

  // Non-stateful hooks (e.g. context) don't get added to memoizedState,

  // so nextCurrentHook would be null during updates and mounts.



  // Delete dev code



    // Call HooksDispatcherOnMount for the first render

    // Multiple render calls to HooksDispatcherOnUpdate



    UseState, useEffect, and other hook functions

    ReactCurrentDispatcher.current =

      nextCurrentHook === null

        ? HooksDispatcherOnMount

        : HooksDispatcherOnUpdate;



  // workinprogress. type = function // workinprogress. type = function

  let children = Component(props, refOrContext);

  // Determine if there is a scheduled update during render execution



  // When there is an update to render

  if (didScheduleRenderPhaseUpdate) {

    do {

      // Set to false to indicate that the loop is executed only once

      didScheduleRenderPhaseUpdate = false;

      // The number of nodes in fiber during re-rendering

      numberOfReRenders += 1;



      // Start over from the beginning of the list

      // Record state to re-execute several useState functions inside the FunctionComponent

nextCurrentHook = current ! = =null ? current.memoizedState : null;

      nextWorkInProgressHook = firstWorkInProgressHook;

      // Release the current state

      currentHook = null;

      workInProgressHook = null;

      componentUpdateQueue = null;



      if (__DEV__) {

        // Also validate hook order for cascading updates.

        hookTypesUpdateIndexDev = - 1;

      }

      //HooksDispatcherOnUpdate

      ReactCurrentDispatcher.current = __DEV__

        ? HooksDispatcherOnUpdateInDEV

        : HooksDispatcherOnUpdate;



      children = Component(props, refOrContext);

    } while (didScheduleRenderPhaseUpdate);



    renderPhaseUpdates = null;

    numberOfReRenders = 0;

  }



  // We can assume the previous dispatcher is always this one, since we set it

  // at the beginning of the render phase and there's no re-entrancy.

  ReactCurrentDispatcher.current = ContextOnlyDispatcher;



  // Define a new fiber object

  const renderedWork: Fiber = (currentlyRenderingFiber: any);

  // Assign a value to the attribute

  renderedWork.memoizedState = firstWorkInProgressHook;

  renderedWork.expirationTime = remainingExpirationTime;

  renderedWork.updateQueue = (componentUpdateQueue: any);

  renderedWork.effectTag |= sideEffectTag;



  if (__DEV__) {

    renderedWork._debugHookTypes = hookTypesDev;

  }



  // This check uses currentHook so that it works the same in DEV and prod bundles.

  // hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.

  const didRenderTooFewHooks =

currentHook ! = =null&& currentHook.next ! = =null;



  / / reset

  renderExpirationTime = NoWork;

  currentlyRenderingFiber = null;



  currentHook = null;

  nextCurrentHook = null;

  firstWorkInProgressHook = null;

  workInProgressHook = null;

  nextWorkInProgressHook = null;



  if (__DEV__) {

    currentHookNameInDev = null;

    hookTypesDev = null;

    hookTypesUpdateIndexDev = - 1;

  }



  remainingExpirationTime = NoWork;

  componentUpdateQueue = null;

  sideEffectTag = 0;



  // These were reset above

  // didScheduleRenderPhaseUpdate = false;

  // renderPhaseUpdates = null;

  // numberOfReRenders = 0;



  invariant(

! didRenderTooFewHooks,

    'Rendered fewer hooks than expected. This may be caused by an accidental ' +

      'early return statement.'.

  );



  return children;

}

Copy the code

Use useState() and useEffect Hook apis instead of using setState when FunctionComponent is used to write React components

So when updating FunctionComponent, renderWithHooks() is executed first to handle these hooks

(1) nextCurrentHook is assigned according to current, so nextCurrentHook can also be used to determine whether it is the first rendering of the component

(2) Both HooksDispatcherOnMount and HooksDispatcherOnUpdate are objects of useState, useEffect and other hook functions:

const HooksDispatcherOnMount: Dispatcher = {

  readContext,



  useCallback: mountCallback,

  useContext: readContext,

  useEffect: mountEffect,

  useImperativeHandle: mountImperativeHandle,

  useLayoutEffect: mountLayoutEffect,

  useMemo: mountMemo,

  useReducer: mountReducer,

  useRef: mountRef,

  useState: mountState,

  useDebugValue: mountDebugValue,

  useEvent: updateEventComponentInstance,

};



const HooksDispatcherOnUpdate: Dispatcher = {

  readContext,



  useCallback: updateCallback,

  useContext: readContext,

  useEffect: updateEffect,

  useImperativeHandle: updateImperativeHandle,

  useLayoutEffect: updateLayoutEffect,

  useMemo: updateMemo,

  useReducer: updateReducer,

  useRef: updateRef,

  useState: updateState,

  useDebugValue: updateDebugValue,

  useEvent: updateEventComponentInstance,

};

Copy the code

As you can see, each Hook API corresponds to an updated method, which we will discuss later

(3) let children = Component(props, refOrContext); Component is workinprogress. type. It can be function or class, but I didn’t think I could call Component(props, refOrContext) as a method.

So I don’t know what children are at the moment, and I’ll mention it in the “preface” if I find anything new.

(4) then when the didScheduleRenderPhaseUpdate is true, perform a while loop, the loop, will save the state of the state, and reset the hooks, components, update the queue is null, Finally, execute Component(props, refOrContext) again to get the new children

DidScheduleRenderPhaseUpdate:

// Whether an update was scheduled during the currently executing render pass.

// Determine if there is a scheduled update during render execution

let didScheduleRenderPhaseUpdate: boolean = false;

Copy the code

This cycle, one of my doubt is, while will didScheduleRenderPhaseUpdate set to false, so the cycle will only perform a, why want to use the while? Why not use if… The else?

There is no answer yet

(5) Define a new Fiber object to keep some variables from which hooks are fixed, and finally set all variables to null, return children

Functions of the reconcileChildren: The ReactElement is turned into a Fiber object and updated to generate corresponding INSTANCES of the DOM, which are mounted to real DOM nodes

Source:

export function reconcileChildren(

  current: Fiber | null,

  workInProgress: Fiber,

  nextChildren: any,

  renderExpirationTime: ExpirationTime,

{

  if (current === null) {

    // If this is a fresh new component that hasn't been rendered yet, we

    // won't update its child set by applying minimal side-effects. Instead,

    // we will add them all to the child before it gets rendered. That means

    // we can optimize this reconciliation pass by not tracking side-effects.



    // Because it is the first rendering, there is no current-child, so the second argument is passed null

    //React first renders the order of the parent nodes, then the child nodes



    workInProgress.child = mountChildFibers(

      workInProgress,

      null.

      nextChildren,

      renderExpirationTime,

    );

  } else {

    // If the current child is the same as the work in progress, it means that

    // we haven't yet started any work on these children. Therefore, we use

    // the clone algorithm to create a copy of all the current children.



    // If we had any progressed work already, that is invalid at this point so

    // let's throw it out.

    workInProgress.child = reconcileChildFibers(

      workInProgress,

      current.child,

      nextChildren,

      renderExpirationTime,

    );

  }

}

Copy the code

The mountChildFibers() and reconcileChildFibers() call the same function ChildReconciler:

//true false

export const reconcileChildFibers = ChildReconciler(true);

export const mountChildFibers = ChildReconciler(false);

Copy the code

False indicates the first rendering,true vice versa

The ChildReconciler reconcileChildren()

This method is more than 1,100 lines, preceded by the definitions of function and finally returned to reconcileChildFibers, so we look from back to front

Source:

// Whether to track side effects

function ChildReconciler(shouldTrackSideEffects{

  xxx

  xxx

  xxx





  function reconcileChildFibers() :Fiber | null {



  }



  return reconcileChildFibers;

}

Copy the code

ShouldTrackSideEffects =true; shouldTrackSideEffects=true; shouldTrackSideEffects=true

This is too long. Look at the reconcileChildFibers at the end

Functions for each reconcileChildFibers: For different types of nodes, there are different node operations

Source:

 // This API will tag the children with the side-effect of the reconciliation

  // itself. They will be added to the side-effect list as we pass through the

  // children and the parent.

  function reconcileChildFibers(

    returnFiber: Fiber,

    currentFirstChild: Fiber | null,

    //The newly calculated children

    newChild: any,

    expirationTime: ExpirationTime,

  
) :Fiber | null 
{

    // This function is not recursive.

    // If the top level item is an array, we treat it as a set of children,

    // not as a fragment. Nested arrays on the other hand will be treated as

    // fragment nodes. Recursion happens at the normal flow.



    // Handle top level unkeyed fragments as if they were arrays.

    // This leads to an ambiguity between <>{[...] } and <>... < / a >.

    // We treat the ambiguous cases above the same.

    const isUnkeyedTopLevelFragment =

      typeof newChild === 'object' &&

newChild ! = =null &&

      // Write
{arr.map((a,b)=> XXX)}
, which is called REACT_FRAGMENT_TYPE


      newChild.type === REACT_FRAGMENT_TYPE &&

      newChild.key === null;

    // A type of REACT_FRAGMENT_TYPE does not require any updates, just render the child nodes

    if (isUnkeyedTopLevelFragment) {

      newChild = newChild.props.children;

    }



    // Handle object types

    const isObject = typeof newChild === 'object'&& newChild ! = =null;

    / / element node

    if (isObject) {

      switch (newChild.?typeof) {

        / / ReactElement node

        case REACT_ELEMENT_TYPE:

          return placeSingleChild(

            reconcileSingleElement(

              returnFiber,

              currentFirstChild,

              newChild,

              expirationTime,

            ),

          );

        //ReactDOM.createPortal(child, container)

        //https://zh-hans.reactjs.org/docs/react-dom.html#createportal

        case REACT_PORTAL_TYPE:

          return placeSingleChild(

            reconcileSinglePortal(

              returnFiber,

              currentFirstChild,

              newChild,

              expirationTime,

            ),

          );

      }

    }

    // Text node

    if (typeof newChild === 'string' || typeof newChild === 'number') {

      return placeSingleChild(

        reconcileSingleTextNode(

          returnFiber,

          currentFirstChild,

          ' ' + newChild,

          expirationTime,

        ),

      );

    }

    // Array node

    if (isArray(newChild)) {

      return reconcileChildrenArray(

        returnFiber,

        currentFirstChild,

        newChild,

        expirationTime,

      );

    }

    //IteratorFunction

    if (getIteratorFn(newChild)) {

      return reconcileChildrenIterator(

        returnFiber,

        currentFirstChild,

        newChild,

        expirationTime,

      );

    }

    // An error is reported if the above element requirements are not met

    if (isObject) {

      throwOnInvalidObjectType(returnFiber, newChild);

    }





    // Delete dev code



    // Display a warning

    if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {

      // If the new child is undefined, and the return fiber is a composite

      // component, throw an error. If Fiber return types are disabled,

      // we already threw above.



      // That is, workInProgress, the node being updated

      switch (returnFiber.tag) {

        case ClassComponent: {

          // Delete dev code



        }

        // Intentionally fall through to the next case, which handles both

        // functions and classes

        // eslint-disable-next-lined no-fallthrough

        case FunctionComponent: {

          const Component = returnFiber.type;

          invariant(

            false.

            '%s(...) : Nothing was returned from render. This usually means a ' +

            'return statement is missing. Or, to render nothing, ' +

            'return null.'.

            Component.displayName || Component.name || 'Component'.

          );

        }

      }

    }



    // Remaining cases are all treated as empty.



    // If the old node exists but the updated node is null, the contents of the old node need to be deleted

    return deleteRemainingChildren(returnFiber, currentFirstChild);

  }

Copy the code

Analytic: (1) isUnkeyedTopLevelFragment wrote such as when we are in development

<div>{ arr.map((a,b)= >xxx) }</div>

Copy the code

REACT_FRAGMENT_TYPE is evaluated as REACT_FRAGMENT_TYPE, and React renders its child nodes directly:

newChild = newChild.props.children;

Copy the code

If element type is object, there are two cases: REACT_ELEMENT_TYPE (ReactElement node); The other is REACT_PORTAL_TYPE, the portal node, which is commonly used in dialogs, hover cards, and prompt boxes. See the official document “Portals”

With REACT_ELEMENT_TYPE, the reconcileSingleElement method is implemented

③ If it is a text node, the reconcileSingleTextNode method is implemented

④ If the last deleteRemainingChildren command is executed, the node to be updated is null, and the content of the original node needs to be deleted

As you can see, the reconcileChildFibers method in ChildReconciler performs different operation node functions based on the node type of the new node newChild

In our next article, we will talk about reconcileSingleElement, reconcileSingleTextNode and deleteRemainingChildren

Making: ReactFiberBeginWork

ReactFiberHooks

ReactChildFiber


(after)