In updateClassComponent (part 1), we discussed the first case of updating a ClassComponent when a class instance was not created.

This article will first discuss the second case where a class instance exists but current is null, i.e. the first rendering:

// First render

  else if (current === null) {

    // In a resume, we'll already have an instance we can reuse.

    // Now that instance is created, update props/state,

    / / call life cycle (componentWillMount, componentDidMount), returns shouldUpdate

    shouldUpdate = resumeMountClassInstance(

      workInProgress,

      Component,

      nextProps,

      renderExpirationTime,

    );

  }

Copy the code

resumeMountClassInstance Reuse ClassComponent instances, update props and state, call the lifecycle API — componentWillMount() and componentDidMount(), and return shouldUpdate: Boolean

Source:

// Reuse the class instance, update the props/state, call the lifecycle, return shouldUpdate

function resumeMountClassInstance(

  workInProgress: Fiber,

  ctor: any,

  newProps: any,

  renderExpirationTime: ExpirationTime,

) :boolean 
{

  // Get the ClassComponent instance

  const instance = workInProgress.stateNode;

  // Get the existing props

  const oldProps = workInProgress.memoizedProps;

  // Initialize props for the class instance

  instance.props = oldProps;

  / / = = = = = can skip the context code = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

  const oldContext = instance.context;

  const contextType = ctor.contextType;

  let nextContext;

  if (typeof contextType === 'object'&& contextType ! = =null) {

    nextContext = readContext(contextType);

  } else {

    const nextLegacyUnmaskedContext = getUnmaskedContext(

      workInProgress,

      ctor,

      true.

    );

    nextContext = getMaskedContext(workInProgress, nextLegacyUnmaskedContext);

  }

  / / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =



  const getDerivedStateFromProps = ctor.getDerivedStateFromProps;

  // From a development perspective, as long as you call getDerivedStateFromProps() or getSnapshotBeforeUpdate()

  // One of the lifecycle apis, the variable hasNewLifecycles, is true

  const hasNewLifecycles =

    typeof getDerivedStateFromProps === 'function' ||

    typeof instance.getSnapshotBeforeUpdate === 'function';



  // Note: During these life-cycles, instance.props/instance.state are what

  // ever the previously attempted to render - not the "current". However,

  // during componentDidUpdate we pass the "current" props.



  // In order to support react-lifecycles-compat polyfilled components,

  // Unsafe lifecycles should not be invoked for components using the new APIs.

  / / if no new method of life cycle, execute componentWillReceiveProps ()

  / / that is to say, if you have getDerivedStateFromProps () or getSnapshotBeforeUpdate (), don't call componentWillReceiveProps method

  if (

! hasNewLifecycles &&

    (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||

      typeof instance.componentWillReceiveProps === 'function')

  ) {

    if(oldProps ! == newProps || oldContext ! == nextContext) {

      callComponentWillReceiveProps(

        workInProgress,

        instance,

        newProps,

        nextContext,

      );

    }

  }

  // Set hasForceUpdate to false

  resetHasForceUpdateBeforeProcessing();

  / / = = = = update updateQueue, access to the new state, and below the callComponentWillMount mountClassInstance logic, the same go = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

  const oldState = workInProgress.memoizedState;

  let newState = (instance.state = oldState);

  let updateQueue = workInProgress.updateQueue;

  if(updateQueue ! = =null) {

    processUpdateQueue(

      workInProgress,

      updateQueue,

      newProps,

      instance,

      renderExpirationTime,

    );

    newState = workInProgress.memoizedState;

  }

  / / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

  // If there is no difference between the old and new props and state, and there is no forceUpdate,

  // Then the component is not updated

  if (

    oldProps === newProps &&

    oldState === newState &&

! hasContextChanged() &&

! checkHasForceUpdateAfterProcessing()

  ) {

    // If an update was already in progress, we should schedule an Update

    // effect even though we're bailing out, so that cWU/cDU are called.

    ComponentDidMount () = componentDidMount();

    if (typeof instance.componentDidMount === 'function') {

      workInProgress.effectTag |= Update;

    }

    //即 shouldUpdate 为 false

    return false;

  }

  / / a call getDerivedStateFromProps (), execute the corresponding applyDerivedStateFromProps

  / / componentWillReceiveProps () and "getDerivedStateFromProps ()/getSnapshotBeforeUpdate ()" are mutually exclusive

  / / this way can perform that componentWillReceiveProps () does not perform

  if (typeof getDerivedStateFromProps === 'function') {

    applyDerivedStateFromProps(

      workInProgress,

      ctor,

      getDerivedStateFromProps,

      newProps,

    );

    newState = workInProgress.memoizedState;

  }

  // Check for forceUpdate and new and old props/state updates

  // shouldUpdate is false only if neither forceUpdate nor props/state changes

  const shouldUpdate =

    checkHasForceUpdateAfterProcessing() ||

    checkShouldComponentUpdate(

      workInProgress,

      ctor,

      oldProps,

      newProps,

      oldState,

      newState,

      nextContext,

    );

  // Execute the corresponding lifecycle methods -- componentWillMount() and componentDidMount() when there are updates

  if (shouldUpdate) {

    // In order to support react-lifecycles-compat polyfilled components,

    // Unsafe lifecycles should not be invoked for components using the new APIs.

    if (

! hasNewLifecycles &&

      (typeof instance.UNSAFE_componentWillMount === 'function' ||

        typeof instance.componentWillMount === 'function')

    ) {

      startPhaseTimer(workInProgress, 'componentWillMount');

      if (typeof instance.componentWillMount === 'function') {

        instance.componentWillMount();

      }

      if (typeof instance.UNSAFE_componentWillMount === 'function') {

        instance.UNSAFE_componentWillMount();

      }

      stopPhaseTimer();

    }

    if (typeof instance.componentDidMount === 'function') {

      workInProgress.effectTag |= Update;

    }

  } else {

    // If an update was already in progress, we should schedule an Update

    // effect even though we're bailing out, so that cWU/cDU are called.

    // Why not update componentDidMount instead?

    // cWU/cDU

    if (typeof instance.componentDidMount === 'function') {

      workInProgress.effectTag |= Update;

    }



    // If shouldComponentUpdate returned false, we should still update the

    // memoized state to indicate that this work can be reused.

    // If no update is required, the props/state is updated to ensure reuse

    // I don't know why

    workInProgress.memoizedProps = newProps;

    workInProgress.memoizedState = newState;

  }



  // Update the existing instance's state, props, and context pointers even

  // if shouldComponentUpdate returns false.

  // Update the related properties to the latest props/state, with or without update

  instance.props = newProps;

  instance.state = newState;

  instance.context = nextContext;

  //boolean

  return shouldUpdate;

}

Copy the code

Resolution:

(1) if there is no call getDerivedStateFromProps () or getSnapshotBeforeUpdate (), call the componentWillReceiveProps () (2) the update updateQueue, If newState (3) is the same as the old and new props, and there is no forceUpdate, the component is not updated, shouldUpdate=false (4) Perform it

Note: Compared with (1), found that componentWillReceiveProps () and “getDerivedStateFromProps ()/getSnapshotBeforeUpdate ()” are mutually exclusive relationship, (4) can perform, are (1) does not perform, Whereas the same

(5) perform checkShouldComponentUpdate (), check for forceUpdate and new and old props/state update. ShouldUpdate is false only if there is no forceUpdate and no props/state change. ComponentWillMount () and componentDidMount() (7); shouldUpdate = false Implement componentDidMount() and update memoizedProps (8) update props and state (9) on instance and return shouldUpdate

Note: about getSnapshotBeforeUpdate () function and usage, please refer to: zh-hans.reactjs.org/docs/react-…

Inside part of the function is simpler, not speak first, then talk about checkShouldComponentUpdate ()

CheckShouldComponentUpdate action: check for props/state update, and determine whether need to perform shouldComponentUpdate () method

Source:

function checkShouldComponentUpdate(

  workInProgress,

  ctor,

  oldProps,

  newProps,

  oldState,

  newState,

  nextContext,

{

  const instance = workInProgress.stateNode;

  // If 'shouldComponentUpdate()' is called, return the result of executing the method

  if (typeof instance.shouldComponentUpdate === 'function') {

    startPhaseTimer(workInProgress, 'shouldComponentUpdate');

    const shouldUpdate = instance.shouldComponentUpdate(

      newProps,

      newState,

      nextContext,

    );

    stopPhaseTimer();



    // Delete the dev code

    / / return true/false

    return shouldUpdate;

  }

  // For pure components, compare props/state with ** shallower **

  if (ctor.prototype && ctor.prototype.isPureReactComponent) {

    return (

      // Shallow equal judgment

! shallowEqual(oldProps, newProps) || ! shallowEqual(oldState, newState)

    );

  }



  return true;

}

Copy the code

Resolution: (1) If shouldComponentUpdate() is called, execute it and return the result, no further. (2) If PureComponent, execute shallowEqual(), Compare props/state with a shallow comparison, and return the result, not continuing further (3) returns true

Note: Shallow comparison judgments about PureComponent, namely shallowEqual(), will be resolved in the next article.

Next, let’s talk about the final case: class instances exist and are rendered multiple times:

//instance! = =null&&current! = =null

//When have instance is created and is not the first time rendering, calls the update method of life cycle for componentWillUpdate, componentDidUpdate (),

  else {

    shouldUpdate = updateClassInstance(

      current,

      workInProgress,

      Component,

      nextProps,

      renderExpirationTime,

    );

  }

Copy the code

UpdateClassInstance function: when you have created instance and not the first time rendering, calls to update the life cycle of the API — componentWillUpdate/componentDidUpdate ()

Source:

// Invokes the update life-cycles and returns false if it shouldn't rerender.

function updateClassInstance(

  current: Fiber,

  workInProgress: Fiber,

  ctor: any,

  newProps: any,

  renderExpirationTime: ExpirationTime,

) :boolean 
{

  const instance = workInProgress.stateNode;



  const oldProps = workInProgress.memoizedProps;

  instance.props =

    workInProgress.type === workInProgress.elementType

      ? oldProps

      : resolveDefaultProps(workInProgress.type, oldProps);



  const oldContext = instance.context;

  const contextType = ctor.contextType;

  let nextContext;

  if (typeof contextType === 'object'&& contextType ! = =null) {

    nextContext = readContext(contextType);

  } else {

    const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);

    nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);

  }



  const getDerivedStateFromProps = ctor.getDerivedStateFromProps;

  const hasNewLifecycles =

    typeof getDerivedStateFromProps === 'function' ||

    typeof instance.getSnapshotBeforeUpdate === 'function';



  // Note: During these life-cycles, instance.props/instance.state are what

  // ever the previously attempted to render - not the "current". However,

  // during componentDidUpdate we pass the "current" props.



  // In order to support react-lifecycles-compat polyfilled components,

  // Unsafe lifecycles should not be invoked for components using the new APIs.

  if (

! hasNewLifecycles &&

    (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||

      typeof instance.componentWillReceiveProps === 'function')

  ) {

    if(oldProps ! == newProps || oldContext ! == nextContext) {

      callComponentWillReceiveProps(

        workInProgress,

        instance,

        newProps,

        nextContext,

      );

    }

  }



  resetHasForceUpdateBeforeProcessing();



  const oldState = workInProgress.memoizedState;

  let newState = (instance.state = oldState);

  let updateQueue = workInProgress.updateQueue;

  if(updateQueue ! = =null) {

    processUpdateQueue(

      workInProgress,

      updateQueue,

      newProps,

      instance,

      renderExpirationTime,

    );

    newState = workInProgress.memoizedState;

  }



  if (

    oldProps === newProps &&

    oldState === newState &&

! hasContextChanged() &&

! checkHasForceUpdateAfterProcessing()

  ) {

    // If an update was already in progress, we should schedule an Update

    // effect even though we're bailing out, so that cWU/cDU are called.



    // Note that this is different from resumeMountClassInstance()

    / / updateClassInstance () : componentDidUpdate/getSnapshotBeforeUpdate

    / / resumeMountClassInstance () : componentDidMount

    if (typeof instance.componentDidUpdate === 'function') {

      if (

oldProps ! == current.memoizedProps ||

oldState ! == current.memoizedState

      ) {

        workInProgress.effectTag |= Update;

      }

    }

    if (typeof instance.getSnapshotBeforeUpdate === 'function') {

      if (

oldProps ! == current.memoizedProps ||

oldState ! == current.memoizedState

      ) {

        workInProgress.effectTag |= Snapshot;

      }

    }

    return false;

  }



  if (typeof getDerivedStateFromProps === 'function') {

    applyDerivedStateFromProps(

      workInProgress,

      ctor,

      getDerivedStateFromProps,

      newProps,

    );

    newState = workInProgress.memoizedState;

  }



  const shouldUpdate =

    checkHasForceUpdateAfterProcessing() ||

    checkShouldComponentUpdate(

      workInProgress,

      ctor,

      oldProps,

      newProps,

      oldState,

      newState,

      nextContext,

    );



  if (shouldUpdate) {

    // In order to support react-lifecycles-compat polyfilled components,

    // Unsafe lifecycles should not be invoked for components using the new APIs.

    // This is also different from resumeMountClassInstance()

    / / updateClassInstance () : componentWillUpdate/componentDidUpdate/getSnapshotBeforeUpdate

    / / resumeMountClassInstance () : componentWillMount/componentDidMount

    if (

! hasNewLifecycles &&

      (typeof instance.UNSAFE_componentWillUpdate === 'function' ||

        typeof instance.componentWillUpdate === 'function')

    ) {

      startPhaseTimer(workInProgress, 'componentWillUpdate');

      if (typeof instance.componentWillUpdate === 'function') {

        instance.componentWillUpdate(newProps, newState, nextContext);

      }

      if (typeof instance.UNSAFE_componentWillUpdate === 'function') {

        instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);

      }

      stopPhaseTimer();

    }

    if (typeof instance.componentDidUpdate === 'function') {

      workInProgress.effectTag |= Update;

    }

    if (typeof instance.getSnapshotBeforeUpdate === 'function') {

      workInProgress.effectTag |= Snapshot;

    }

  } else {

    // If an update was already in progress, we should schedule an Update

    // effect even though we're bailing out, so that cWU/cDU are called.

    if (typeof instance.componentDidUpdate === 'function') {

      if (

oldProps ! == current.memoizedProps ||

oldState ! == current.memoizedState

      ) {

        workInProgress.effectTag |= Update;

      }

    }

    if (typeof instance.getSnapshotBeforeUpdate === 'function') {

      if (

oldProps ! == current.memoizedProps ||

oldState ! == current.memoizedState

      ) {

        workInProgress.effectTag |= Snapshot;

      }

    }



    // If shouldComponentUpdate returned false, we should still update the

    // memoized props/state to indicate that this work can be reused.

    workInProgress.memoizedProps = newProps;

    workInProgress.memoizedState = newState;

  }



  // Update the existing instance's state, props, and context pointers even

  // if shouldComponentUpdate returns false.

  instance.props = newProps;

  instance.state = newState;

  instance.context = nextContext;



  return shouldUpdate;

}

Copy the code

ResumeMountClassInstance () ¶ resumeMountClassInstance() ¶ resumeMountClassInstance() ¶ resumeMountClassInstance() ¶

After executing in all three cases, updateFunctionComponent() finally executes the finishClassComponent() method to determine if render is required:

// Determine if render is executed and return the first child under render

  const nextUnitOfWork = finishClassComponent(

    current,

    workInProgress,

    Component,

    shouldUpdate,

    hasContext,

    renderExpirationTime,

  );

Copy the code

Next, let’s look at the method

Render (); finishClassComponent returns the first child under Render

Source:

// Determine if render is executed and return the first child under render

function finishClassComponent(

  current: Fiber | null,

  workInProgress: Fiber,

  Component: any,

  shouldUpdate: boolean,

  hasContext: boolean,

  renderExpirationTime: ExpirationTime,

{

  // Refs should update even if shouldComponentUpdate returns false

  // The ref reference must be updated regardless of whether the props/state is updated

  markRef(current, workInProgress);

  // Determine if there is an error catch

  constdidCaptureError = (workInProgress.effectTag & DidCapture) ! == NoEffect;

  // When no update is required/the update is complete and no error occurs

  if(! shouldUpdate && ! didCaptureError) {

    // Context providers should defer to sCU for rendering

    if (hasContext) {

      invalidateContextProvider(workInProgress, Component, false);

    }

    // Skip the update of the node on the class and all its children, i.e. skip calling the render method

    return bailoutOnAlreadyFinishedWork(

      current,

      workInProgress,

      renderExpirationTime,

    );

  }



  const instance = workInProgress.stateNode;



  // Rerender

  ReactCurrentOwner.current = workInProgress;

  let nextChildren;

  / / getDerivedStateFromError API, is life cycle is used to capture render error, see:

  //https://zh-hans.reactjs.org/docs/react-component.html#static-getderivedstatefromerror

  if (

    didCaptureError &&

    typeofComponent.getDerivedStateFromError ! = ='function'

  ) {

    // If we captured an error, but getDerivedStateFrom catch is not defined,

    // unmount all the children. componentDidCatch will schedule an update to

    // re-render a fallback. This is temporary until we migrate everyone to

    // the new API.

    // TODO: Warn in a future release.

    // Render is interrupted if an error occurs but the developer does not call getDerivedStateFromError

    nextChildren = null;



    if (enableProfilerTimer) {

      stopProfilerTimerIfRunning(workInProgress);

    }

  }

  // Otherwise re-render

  else {

    // Delete dev code

    if (__DEV__) {



    } else {

      nextChildren = instance.render();

    }

  }



  // React DevTools reads this flag.

  workInProgress.effectTag |= PerformedWork;

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

    // If we're recovering from an error, reconcile without reusing any of

    // the existing children. Conceptually, the normal children and the children

    // that are shown on error are two different sets, so we shouldn't reuse

    // normal children even if their identities match.

    // Force a recalculation of children, because when an error occurs, the props/state on the render node cannot be reused and must be re-rendered

    forceUnmountCurrentAndReconcile(

      current,

      workInProgress,

      nextChildren,

      renderExpirationTime,

    );

  } else {

    // 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,

    );

  }



  // Memoize state using the values we just used to render.

  // TODO: Restructure so we never read values from the instance.

  workInProgress.memoizedState = instance.state;



  // The context might have changed so we need to recalculate it.

  if (hasContext) {

    invalidateContextProvider(workInProgress, Component, true);

  }

  // Returns the first node under render

  return workInProgress.child;

}

Copy the code

(1) If the props/state is updated, the ref reference must be updated.

function markRef(current: Fiber | null, workInProgress: Fiber{

  const ref = workInProgress.ref;

  if (

    (current === null && ref! = =null) | |

(current ! = =null && current.ref! = =ref)

  ) {

    // Schedule a Ref effect

    workInProgress.effectTag |= Ref;

  }

}

Copy the code

(2) to determine whether there is an error to capture, assigned to didCaptureError (3) when I do not need to update/update, and there is no capture by the time the error, the execution bailoutOnAlreadyFinishedWork (), Skip the update of the node on the ClassInstance and all of its children by calling the Render method

About bailoutOnAlreadyFinishedWork (), please see: the React workLoop source code parsing

(4) If an error is caught and the developer does not call getDerivedStateFromError, break the rendering and set nextChildren to null

About getDerivedStateFromError (), please see: zh-hans.reactjs.org/docs/react-…

(5) if not capture the error, the execution instance. The render (), to render, and returns the nextChildren (6) rendering, if caught the error, the execution forceUnmountCurrentAndReconcile (), Mandatory recalculation of children; Otherwise, the reconcileChildren() is executed to turn the ReactElement into a Fiber object, which is updated to generate corresponding instances of the DOM and mount them to the real DOM nodes. Returns the first node under Render, workinProgress.child

The flow chart for finishClassComponent() looks like this:

OK, that’s the end of updateClassComponent(). Shallow comparison judgments about PureComponent, shallowEqual(), will be explained in the next article.

Making: github.com/AttackXiaoJ…


(after)