Capture phase

This chapter covers the capture phase of the Render phase in detail. So let’s start with beginWork, the entry function for the capture phase.

beginWork

BeginWork function in the ReactFiberBeginWork. Old. Js, there are about 5600 lines of all functions, we explain line by line, as long as pay attention to one of the most important logic.

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
) :Fiber | null {
  const updateLanes = workInProgress.lanes;
	// If current is not empty, it indicates that the current phase is update and some optimizations will be performed
  if(current ! = =null) {
    / /... omit
  }
  
  workInProgress.lanes = NoLanes;

  switch (workInProgress.tag) {
  	/ /... omit}}Copy the code

First let’s look at the parameters:

  • Current: indicates the last updated node, that is, the current rendered node, if present, then workinProgress. alternate should point to the same object
  • WorkInProgress: Fiber currently being updated
  • RenderLanes: Priority-dependent, more on Concurrent mode later

As you can see from the simplified code above, beignWork has two important logical components: if(current! == null) and switch. So what are these two parts for?

The current is not null

Under what circumstances is current not empty? Alternate: workinprogress. alternate: workinprogress. alternate: workinprogress. alternate: workInProgress. Workinprogress. alternate refers to the node currently rendered. If current is not null, the node has been mounted, and the operation on this node is an update operation.

The if branch does several things:

  • contrastoldPropsandnewPropsTo determine whether an update is required
  • Check whether the current fiber priority is included in the update priority. If not, the update will not be carried out
if(current ! = =null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;
    // Check whether the current fiber node has changed.
    if(oldProps ! == newProps || hasLegacyContextChanged() || (__DEV__ ? workInProgress.type ! == current.type :false)
    ) {
      didReceiveUpdate = true;
    } else if(! includesSomeLane(renderLanes, updateLanes)) {// The priority of the current fiber is not included in the priority of the current update. The original fiber is reused directly instead of the following update comparison to improve efficiency
      didReceiveUpdate = false;
      switch (workInProgress.tag) {
          / /... omit
          // Different tags are processed differently
      }
      RootFiber. Lanes = 0; rootFiber
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    } else {
      // There is only one special case of the traditional pattern
      if((current.flags & ForceUpdateForLegacySuspense) ! == NoFlags) { didReceiveUpdate =true;
      } else {
        didReceiveUpdate = false; }}}else {
    didReceiveUpdate = false;
  }
Copy the code

What does the Switch do

switch

We’re left with a few important cases, and we can see from the code that SWTCH simply calls different Update methods based on different tags and returns its children.

 switch (workInProgress.tag) {
    / /... omit
    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);
   / /... omit
  }
Copy the code

So what do these update functions do? Let’s use updateClassComponent and updateClassComponent as examples.

updateClassComponent

  • Current: Fiber in the current rendering
  • WorkInProgress: workInProgress
  • Component: Class object, not class instance
  • NextProps: props used for this creation
  • RenderLanes: Specifies the priority of this rendering

function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderLanes: Lanes,
) {
  // Process context. We can ignore it for now
  let hasContext;
  if (isLegacyContextProvider(Component)) {
    hasContext = true;
    pushLegacyContextProvider(workInProgress);
  } else {
    hasContext = false;
  }
  prepareToReadContext(workInProgress, renderLanes);
	// Get an instance of Class
  const instance = workInProgress.stateNode;
  let shouldUpdate;
   // If it does not exist, call new to create an instance of Class
  if (instance === null) {
    if(current ! = =null) {
      current.alternate = null;
      workInProgress.alternate = null;
      workInProgress.flags |= Placement;
    }
    constructClassInstance(workInProgress, Component, nextProps);
    mountClassInstance(workInProgress, Component, nextProps, renderLanes);
    shouldUpdate = true;
  } else if (current === null) {
    shouldUpdate = resumeMountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderLanes,
    );
  } else {
    shouldUpdate = updateClassInstance(
      current,
      workInProgress,
      Component,
      nextProps,
      renderLanes,
    );
  }
  const nextUnitOfWork = finishClassComponent(
    current,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderLanes,
  );
  return nextUnitOfWork;
}

Copy the code

This function is used to update ClassCompenent, so what does it do?

  • To deal with the context
  • Checks if instance (class instance) exists and creates it if it does notconstructClassInstanceThe Class constructor creates an instance of ClassconstructorIs called
  • Mount or update (mountClassInstance/updateClassInstance)
  • finishClassComponent

So let’s see what the mountClassInstance, updateClassInstance, and finishClassComponent functions do.

mountClassInstance

  • Compute the latest state
  • Call the getDerivedStateFromProps lifecycle
  • Call componentWillMount and UNSAFE_componentWillMount life cycle
function mountClassInstance(workInProgress: Fiber, ctor: any, newProps: any, renderLanes: Lanes,) :void {
	/ /... omit
  // Calculate the latest state based on updateQueue
  processUpdateQueue(workInProgress, newProps, instance, renderLanes);
  instance.state = workInProgress.memoizedState;
 	// Call the getDerivedStateFromProps lifecycle
  const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
  if (typeof getDerivedStateFromProps === 'function') {
    applyDerivedStateFromProps(
      workInProgress,
      ctor,
      getDerivedStateFromProps,
      newProps,
    );
    instance.state = workInProgress.memoizedState;
  }
	
  if (
    typeofctor.getDerivedStateFromProps ! = ='function' &&
    typeofinstance.getSnapshotBeforeUpdate ! = ='function' &&
    (typeof instance.UNSAFE_componentWillMount === 'function' ||
      typeof instance.componentWillMount === 'function')) {// Call componentWillMount and UNSAFE_componentWillMount life cycle
    callComponentWillMount(workInProgress, instance);
    // If we had additional state updates during this life-cycle, let's
    // process them now.
    processUpdateQueue(workInProgress, newProps, instance, renderLanes);
    instance.state = workInProgress.memoizedState;
  }

  if (typeof instance.componentDidMount === 'function') {
    if (__DEV__ && enableDoubleInvokingEffects) {
      workInProgress.flags |= MountLayoutDev | Update;
    } else{ workInProgress.flags |= Update; }}}Copy the code

updateClassInstance

  • Call UNSAFE_componentWillReceiveProps, componentWillReceiveProps life cycle
  • Calculation of the state
  • Call the getDerivedStateFromProps lifecycle,
  • Call shouldComponentUpdate life cycle
  • Call the componentWillUpdate and UNSAFE_componentWillUpdate life cycle
function updateClassInstance(current: Fiber, workInProgress: Fiber, ctor: any, newProps: any, renderLanes: Lanes,) :boolean {
  const instance = workInProgress.stateNode;

  cloneUpdateQueue(current, workInProgress);

  const unresolvedOldProps = workInProgress.memoizedProps;
  const oldProps =
    workInProgress.type === workInProgress.elementType
      ? unresolvedOldProps
      : resolveDefaultProps(workInProgress.type, unresolvedOldProps);
  instance.props = oldProps;
  const unresolvedNewProps = workInProgress.pendingProps;

  const oldContext = instance.context;
  const contextType = ctor.contextType;
  let nextContext = emptyContextObject;
  if (typeof contextType === 'object'&& contextType ! = =null) {
    nextContext = readContext(contextType);
  } else if(! disableLegacyContext) {const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
  }
  const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
  const hasNewLifecycles =
    typeof getDerivedStateFromProps === 'function' ||
    typeof instance.getSnapshotBeforeUpdate === 'function';
   / / call UNSAFE_componentWillReceiveProps, componentWillReceiveProps life cycle
  if (
    !hasNewLifecycles &&
    (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
      typeof instance.componentWillReceiveProps === 'function')) {if( unresolvedOldProps ! == unresolvedNewProps || oldContext ! == nextContext ) { callComponentWillReceiveProps( workInProgress, instance, newProps, nextContext, ); } } resetHasForceUpdateBeforeProcessing();const oldState = workInProgress.memoizedState;
  let newState = (instance.state = oldState);
  / / state
  processUpdateQueue(workInProgress, newProps, instance, renderLanes);
  newState = workInProgress.memoizedState;
	// Check whether it is updated
  if( unresolvedOldProps === unresolvedNewProps && 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.
    if (typeof instance.componentDidUpdate === 'function') {
      if( unresolvedOldProps ! == current.memoizedProps || oldState ! == current.memoizedState ) { workInProgress.flags |= Update; }}if (typeof instance.getSnapshotBeforeUpdate === 'function') {
      if( unresolvedOldProps ! == current.memoizedProps || oldState ! == current.memoizedState ) { workInProgress.flags |= Snapshot; }}return false;
  }
	// Call the getDerivedStateFromProps lifecycle
  if (typeof getDerivedStateFromProps === 'function') {
    applyDerivedStateFromProps(
      workInProgress,
      ctor,
      getDerivedStateFromProps,
      newProps,
    );
    newState = workInProgress.memoizedState;
  }
	// Call shouldComponentUpdate lifecycle
  // Check whether isPureReactComponent is true
  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.
    if (
      !hasNewLifecycles &&
      (typeof instance.UNSAFE_componentWillUpdate === 'function' ||
        typeof instance.componentWillUpdate === 'function')) {if (typeof instance.componentWillUpdate === 'function') {
        / / call componentWillUpdate
        instance.componentWillUpdate(newProps, newState, nextContext);
      }
      if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
        // Call the UNSAFE_componentWillUpdate life cycleinstance.UNSAFE_componentWillUpdate(newProps, newState, nextContext); }}// Add the update tag
    if (typeof instance.componentDidUpdate === 'function') {
      workInProgress.flags |= Update;
    }
    if (typeof instance.getSnapshotBeforeUpdate === 'function') { workInProgress.flags |= 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( unresolvedOldProps ! == current.memoizedProps || oldState ! == current.memoizedState ) { workInProgress.flags |= Update; }}if (typeof instance.getSnapshotBeforeUpdate === 'function') {
      if( unresolvedOldProps ! == current.memoizedProps || oldState ! == current.memoizedState ) { workInProgress.flags |= 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

Ok, let’s look at the final function, finishClassComponent

finishClassComponent

  • Call the render method
  • Call the diff algorithm entryreconcileChildren
function finishClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  shouldUpdate: boolean,
  hasContext: boolean,
  renderLanes: Lanes,
) {
    // Refs should update even if shouldComponentUpdate returns false
    // Flag whether the current fiber has a ref
    markRef(current, workInProgress);

    constdidCaptureError = (workInProgress.flags & DidCapture) ! == NoFlags;/ / optimization
    if(! shouldUpdate && ! didCaptureError) {if (hasContext) {
        invalidateContextProvider(workInProgress, Component, false);
      }
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    }

		// Get the current instance, new Class
    const instance = workInProgress.stateNode;

    // Rerender
    ReactCurrentOwner.current = workInProgress;
    let nextChildren;
    if (
      didCaptureError &&
      typeofComponent.getDerivedStateFromError ! = ='function'
    ) {
      nextChildren = null;
      if(enableProfilerTimer) { stopProfilerTimerIfRunning(workInProgress); }}else {
      // Omit some code
      / / call the render
      nextChildren = instance.render();
    }
    workInProgress.flags |= PerformedWork;
    if(current ! = =null && didCaptureError) {
      // Call the diff algorithm
      forceUnmountCurrentAndReconcile(
        current,
        workInProgress,
        nextChildren,
        renderLanes,
      );
    } else {
      // Call the diff algorithm
      reconcileChildren(current, workInProgress, nextChildren, renderLanes);
    }
		/ /... omit
    return workInProgress.child;
}
Copy the code

updateFunctionComponent

  • Call functions and hooks functions
  • Call the diff entry
function updateFunctionComponent(current, workInProgress, Component, nextProps: any, renderLanes,) {
 	/ /... Omit, deal with context
  if (__DEV__) {
    / /... omit
  } else {
    // call functions and hooks
    nextChildren = renderWithHooks(
      current,
      workInProgress,
      Component,
      nextProps,
      context,
      renderLanes,
    );
  }
	// Performance optimization
  if(current ! = =null && !didReceiveUpdate) {
    bailoutHooks(current, workInProgress, renderLanes);
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }

  // Call the diff entry
  workInProgress.flags |= PerformedWork;
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}
Copy the code

updateHostComponent

  • Call the diff entry
function updateHostComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
) {
  / /... Ignore, process the context
  // Determine if the child node is a unique text node, if so, optimize to complete
  const isDirectTextChild = shouldSetTextContent(type, nextProps);

  if (isDirectTextChild) {
    nextChildren = null;
  } else if(prevProps ! = =null && shouldSetTextContent(type, prevProps)) {
    workInProgress.flags |= ContentReset;
  }
	// mark the ref mark
  markRef(current, workInProgress);
  // diff
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}
Copy the code

conclusion

As you can see, the updateClassComponent, updateFunctionComponent, and updateHostComponent functions all end up calling reconcileChildren, which is the entry to the Diff algorithm. Let’s press the button first, and in the next chapter we’ll look at the bubbling stage.

This article has some references to React technology debunking