React 源码解读之class组件更新 updateClassComponent (三)

564 阅读9分钟

这是我参与2022首次更文挑战的第12天,活动详情查看:2022首次更文挑战」。

react 版本:v17.0.3

ClassComponent的三种更新情形

在《React 源码解读之class组件更新updateClassComponent (一)》一文中我们提到,对于 ClassComponent 的更新,有三种情形:

  • 情形一:ClassComponent实例未被创建,此时会调用 constructClassInstance 方法构建class组件实例,然后调用 mountClassInstance 方法挂载class组件,并将 shouldUpdate 置为 true,标记组件需要更新渲染。

  • 情形二:ClassComponent实例已经存在,但current(当前渲染在界面上的fiber树) 为null,即 ClassComponent 是初次渲染,此时调用 resumeMountClassInstance 方法,复用ClassComponent实例,并更新 state/props,然后会执行 componentWillMount 生命周期函数。

  • 情形三:ClassComponent实例已经存在,且已经是多次渲染,此时调用 updateClassInstance 方法执行更新操作,且会执行 componentWillUpdate 生命周期函数。

在本文中,我们将对 ClassComponent 更新的第二种情形进行解读。

// react-reconciler/src/ReactFiberBeginWork.new.js

else if (current === null) {
  // In a resume, we'll already have an instance we can reuse.
  // class组件实例已经存在,但是 current 为 null,即 ClassComponent 是初次渲染
  // 那么复用 class组件实例,更新 state/prop,并返回 shouldUpdate
  shouldUpdate = resumeMountClassInstance(
    workInProgress,
    Component,
    nextProps,
    renderLanes,
  );
}

情形二:ClassComponent实例已经存在

在 ClassComponent 更新的第二种情形中,会调用 resumeMountClassInstance 方法,复用ClassComponent实例,并更新 state/props,然后会执行 componentWillMount 生命周期函数。

我们先来详细解析下ClassComponent的第二种更新情形。

resumeMountClassInstance

resumeMountClassInstance 函数的主要作用:复用 类实例,更新 state/props,执行生命周期函数,返回 shouldUpdate

// react-reconciler/src/ReactFiberClassComponent.new.js

function resumeMountClassInstance(
    workInProgress: Fiber,
    ctor: any,
    newProps: any,
    renderLanes: Lanes,
  ): boolean {
    // 从当前工作的 fiber 对象上获取 ClassComponent实例
    const instance = workInProgress.stateNode;
    // 获取已有的 props 
    const oldProps = workInProgress.memoizedProps;
    // 将已有的 props 赋值给 ClassComponent实例,从而初始化 ClassComponent实例的 props
    instance.props = oldProps;
  
    // 获取 ClassComponent实例上已有的 context
    const oldContext = instance.context;
    // 获取开发中定义的类组件定义的 context
    const contextType = ctor.contextType;
    // 初始化新的 context
    let nextContext = emptyContextObject;
    if (typeof contextType === 'object' && contextType !== null) {
      nextContext = readContext(contextType);
    } else if (!disableLegacyContext) {
      const nextLegacyUnmaskedContext = getUnmaskedContext(
        workInProgress,
        ctor,
        true,
      );
      nextContext = getMaskedContext(workInProgress, nextLegacyUnmaskedContext);
    }
  
    // getDerivedStateFromProps 是新的生命周期函数
    // 获取 ClassComponent 上的 getDerivedStateFromProps 生命周期函数,
    // 如果在在开发时提供了 getDerivedStateFromProps 钩子
    const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
    // 在开发时,在 ClassComponent 中只要调用了getDerivedStateFromProps() 或 getSnapshotBeforeUpdate() 
    // 那么 hasNewLifecycles 变量就为 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.
  
    // 如果没有使用新的生命周期函数,则执行旧的生命周期函数 componentWillReceiveProps()
    if (
      !hasNewLifecycles &&
      (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
        typeof instance.componentWillReceiveProps === 'function')
    ) {
      if (oldProps !== newProps || oldContext !== nextContext) {
        // 执行 componentWillReceiveProps() 生命周期函数
        callComponentWillReceiveProps(
          workInProgress,
          instance,
          newProps,
          nextContext,
        );
      }
    }
  
    // 设置 hasForceUpdate 为 false,即表示不需要强制更新
    resetHasForceUpdateBeforeProcessing();
  
    // 更新 updateQueue
    // 和 ClassComponent更新的情形一中的 mountClassInstance 下的 processUpdateQueue 的逻辑相同
    const oldState = workInProgress.memoizedState;
    let newState = (instance.state = oldState);
    processUpdateQueue(workInProgress, newProps, instance, renderLanes);
    newState = workInProgress.memoizedState;
    // 新旧 props 和 state 没有差别,且没有 forceUpdate 的情况,那么组件是不需要更新的
    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.
  
      // 因为 ClassComponent 是初次渲染,所以需要调用 componentDidMount() 来挂载组件
      if (typeof instance.componentDidMount === 'function') {
        let fiberFlags: Flags = Update;
        if (enableSuspenseLayoutEffectSemantics) {
          fiberFlags |= LayoutStatic;
        }
        
        // 删除了 Dev 部分的代码

        workInProgress.flags |= fiberFlags;
      }
      // 即 shouldUpdate 为 false,不需要执行更新
      return false;
    }
  
    // 如果有调用 getDerivedStateFromProps 生命周期函数,
    // 则执行对应的 getDerivedStateFromProps 生命周期函数
    if (typeof getDerivedStateFromProps === 'function') {
      applyDerivedStateFromProps(
        workInProgress,
        ctor,
        getDerivedStateFromProps,
        newProps,
      );
      newState = workInProgress.memoizedState;
    }
  
    // 检查是否有 forceUpdate 或者 新老 props/state 的更新
    const shouldUpdate =
      checkHasForceUpdateAfterProcessing() ||
      checkShouldComponentUpdate(
        workInProgress,
        ctor,
        oldProps,
        newProps,
        oldState,
        newState,
        nextContext,
      );
  
    // 需要更新,执行相应的生命周期函数 componentWillMount、UNSAFE_componentWillMount 和 componentDidMount
    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')
      ) {
        if (typeof instance.componentWillMount === 'function') {
          instance.componentWillMount();
        }
        if (typeof instance.UNSAFE_componentWillMount === 'function') {
          instance.UNSAFE_componentWillMount();
        }
      }
      if (typeof instance.componentDidMount === 'function') {
        let fiberFlags: Flags = Update;
        if (enableSuspenseLayoutEffectSemantics) {
          fiberFlags |= LayoutStatic;
        }
        
        // 删除了 Dev 部分的代码

        workInProgress.flags |= fiberFlags;
      }
    } 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.
  
      // 执行 componentDidMount 生命周期函数
      if (typeof instance.componentDidMount === 'function') {
        let fiberFlags: Flags = Update;
        if (enableSuspenseLayoutEffectSemantics) {
          fiberFlags |= LayoutStatic;
        }
        
        // 删除了 Dev 部分的代码

        workInProgress.flags |= fiberFlags;
      }
  
      // If shouldComponentUpdate returned false, we should still update the
      // memoized state to indicate that this work can be reused.
      // 即使不需要更新,也会更新原有的 props/state,以保证可以复用
      workInProgress.memoizedProps = newProps;
      workInProgress.memoizedState = newState;
    }
  
    // Update the existing instance's state, props, and context pointers even
    // if shouldComponentUpdate returns false.
  
    // 更新props/state 为最新的 props/state,无论是否有 update
    instance.props = newProps;
    instance.state = newState;
    instance.context = nextContext;
  
    return shouldUpdate;
  }
  

在 resumeMountClassInstance 函数中,做了以下事情:

1、首先通过stateNode属性从workInProgress上获取class组件实例,并从workInProgress上获取已有的 props、context 等。

// 从当前工作的 fiber 对象上获取 ClassComponent实例
const instance = workInProgress.stateNode;

// 获取已有的 props 
const oldProps = workInProgress.memoizedProps;
// 将已有的 props 赋值给 ClassComponent实例,从而初始化 ClassComponent实例的 props
instance.props = oldProps;

// 获取 ClassComponent实例上已有的 context
const oldContext = instance.context;
// 获取开发中定义的类组件中定义的 context
const contextType = ctor.contextType;
// 初始化新的 context
let nextContext = emptyContextObject;
if (typeof contextType === 'object' && contextType !== null) {
  nextContext = readContext(contextType);
} else if (!disableLegacyContext) {
  const nextLegacyUnmaskedContext = getUnmaskedContext(
    workInProgress,
    ctor,
    true,
  );
  nextContext = getMaskedContext(workInProgress, nextLegacyUnmaskedContext);
}

2、接着判断开发中的class组件是否调用了 getDerivedStateFromProps() 或 getSnapshotBeforeUpdate() 生命周期函数,如果没有,则执行旧的生命周期函数 componentWillReceiveProps() 。

  // getDerivedStateFromProps 是新的生命周期函数
  // 获取 ClassComponent 上的 getDerivedStateFromProps 生命周期函数,
  // 如果在在开发时提供了 getDerivedStateFromProps 钩子
  const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
  // 在开发时,在 ClassComponent 中只要调用了getDerivedStateFromProps() 或 getSnapshotBeforeUpdate() 
  // 那么 hasNewLifecycles 变量就为 true
  const hasNewLifecycles =
    typeof getDerivedStateFromProps === 'function' ||
    typeof instance.getSnapshotBeforeUpdate === 'function';

  // 如果没有使用新的生命周期函数,则执行旧的生命周期函数 componentWillReceiveProps()
  if (
    !hasNewLifecycles &&
    (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
      typeof instance.componentWillReceiveProps === 'function')
  ) {
    if (oldProps !== newProps || oldContext !== nextContext) {
      // 执行 componentWillReceiveProps() 生命周期函数
      callComponentWillReceiveProps(
        workInProgress,
        instance,
        newProps,
        nextContext,
      );
    }
  }

3、接着调用 resetHasForceUpdateBeforeProcessing 方法将 hasForceUpdate 设为 false ,即表示不需要强制更新。

// 设置 hasForceUpdate 为 false,即表示不需要强制更新
resetHasForceUpdateBeforeProcessing();

4、接下来调用 processUpdateQueue() 来更新 updateQueue,获取新的 state。

// 更新 updateQueue
// 和 ClassComponent更新的情形一中的 mountClassInstance 下的 processUpdateQueue 的逻辑相同
const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
processUpdateQueue(workInProgress, newProps, instance, renderLanes);
newState = workInProgress.memoizedState;

5、获取新的state后,判断新旧的 state 和 props 是否有差别,如果没有,并且也没有 forceUpdate 的情况,那么组件是不需要更新的,返回的 shouldUpdate 为 false 。

// 新旧 props 和 state 没有差别,且没有 forceUpdate 的情况,那么组件是不需要更新的
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.

  // 因为 ClassComponent 是初次渲染,所以需要调用 componentDidMount() 来挂载组件
  if (typeof instance.componentDidMount === 'function') {
    let fiberFlags: Flags = Update;
    if (enableSuspenseLayoutEffectSemantics) {
      fiberFlags |= LayoutStatic;
    }
    
    // ...
    
    workInProgress.flags |= fiberFlags;
  }
  // 即 shouldUpdate 为 false,不需要执行更新
  return false;
}

6、如果开发中的class组件调用了 getDerivedStateFromProps 生命周期函数,则调用 applyDerivedStateFromProps 方法来执行 getDerivedStateFromProps 生命周期函数。

// 如果有调用 getDerivedStateFromProps 生命周期函数,
// 则执行对应的 getDerivedStateFromProps 生命周期函数
if (typeof getDerivedStateFromProps === 'function') {
  applyDerivedStateFromProps(
    workInProgress,
    ctor,
    getDerivedStateFromProps,
    newProps,
  );
  newState = workInProgress.memoizedState;
}

7、然后执行checkHasForceUpdateAfterProcessing 和 checkShouldComponentUpdate 来检查是否有 forceUpdate 和 新旧 state/props 的更新,并将其检查结果赋值给 shouldUpdate 变量。

// 检查是否有 forceUpdate 或者 新老 props/state 的更新
const shouldUpdate =
  checkHasForceUpdateAfterProcessing() ||
  checkShouldComponentUpdate(
    workInProgress,
    ctor,
    oldProps,
    newProps,
    oldState,
    newState,
    nextContext,
  );

8、如果检查后的结果是需要更新,即 shouldUpdate 为true,那么组件需要更新,判断在 class组件实例上是否调用了 componentWillMount() 和 componentDidMount() 生命周期函数,若有,则执行这两个生命周期函数。

// 需要更新,执行相应的生命周期函数 componentWillMount、UNSAFE_componentWillMount 和 componentDidMount
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')
  ) {
    if (typeof instance.componentWillMount === 'function') {
      instance.componentWillMount();
    }
    if (typeof instance.UNSAFE_componentWillMount === 'function') {
      instance.UNSAFE_componentWillMount();
    }
  }
  if (typeof instance.componentDidMount === 'function') {
    let fiberFlags: Flags = Update;
    if (enableSuspenseLayoutEffectSemantics) {
      fiberFlags |= LayoutStatic;
    }
    
    // ...
    
    workInProgress.flags |= fiberFlags;
  }
}

9、如果检查的结果是不需要更新,即 shouldUpdate 为 false,仍会判断执行 componentDidMount() 生命周期函数,并更新当前工作的 fiber 对象的 memoizedProps 和 memoizedState,以便可以复用。

else {
 
  // 执行 componentDidMount 生命周期函数
  if (typeof instance.componentDidMount === 'function') {
    let fiberFlags: Flags = Update;
    if (enableSuspenseLayoutEffectSemantics) {
      fiberFlags |= LayoutStatic;
    }
    
    // ...
    
    workInProgress.flags |= fiberFlags;
  }

  // 即使不需要更新,也会更新原有的 props/state,以保证可以复用
  workInProgress.memoizedProps = newProps;
  workInProgress.memoizedState = newState;
}

10、最后更新class组件实例上的 props 和 state,并将 shouldUpdate 的结果返回。

// 更新props/state 为最新的 props/state,无论是否有 update
instance.props = newProps;
instance.state = newState;
instance.context = nextContext;

return shouldUpdate;

接下来,我们来逐个看看在 resumeMountClassInstance 中调用的函数。

callComponentWillReceiveProps

// react-reconciler/src/ReactFiberClassComponent.new.js

function callComponentWillReceiveProps(
  workInProgress,
  instance,
  newProps,
  nextContext,
) {
  const oldState = instance.state;
  // 执行 componentWillReceiveProps 生命周期函数
  if (typeof instance.componentWillReceiveProps === 'function') {
    instance.componentWillReceiveProps(newProps, nextContext);
  }
  // 执行 UNSAFE_componentWillReceiveProps 生命周期函数 
  if (typeof instance.UNSAFE_componentWillReceiveProps === 'function') {
    instance.UNSAFE_componentWillReceiveProps(newProps, nextContext);
  }

  if (instance.state !== oldState) {
    
    // ...

    // 执行替换更新
    classComponentUpdater.enqueueReplaceState(instance, instance.state, null);
  }
}

可以看到,在 callComponentWillReceiveProps 函数中,判断在class组件实例上是否调用了 componentWillReceiveProps 和 UNSAFE_componentWillReceiveProps 生命周期函数,如果有,则执行这两个生命周期函数。

在执行完componentWillReceiveProps 和 UNSAFE_componentWillReceiveProps 生命周期函数后,如果class组件实例上的 state 和 旧的 state不一样,则调用 enqueueReplaceState 方法执行替换更新。

resetHasForceUpdateBeforeProcessing

// react-reconciler/src/ReactUpdateQueue.new.js

export function resetHasForceUpdateBeforeProcessing() {
  hasForceUpdate = false;
}

可以看到,在 resetHasForceUpdateBeforeProcessing 函数中,将全局变量hasForceUpdate设为了 false,表示不需要强制更新。

applyDerivedStateFromProps

applyDerivedStateFromProps 的作用是 执行 getDerivedStateFromProps 生命周期函数,并更新 state

// react-reconciler/src/ReactFiberClassComponent.new.js

function applyDerivedStateFromProps(
  workInProgress: Fiber,
  ctor: any,
  getDerivedStateFromProps: (props: any, state: any) => any,
  nextProps: any,
) {
  const prevState = workInProgress.memoizedState;
  // 执行 getDerivedStateFromProps 生命周期函数
  let partialState = getDerivedStateFromProps(nextProps, prevState);
  
  // ...
    
  // Merge the partial state and the previous state.
  // 合并新的state和旧的state
  const memoizedState =
    partialState === null || partialState === undefined
      ? prevState
      : Object.assign({}, prevState, partialState);
  // 更新state   
  workInProgress.memoizedState = memoizedState;

  // Once the update queue is empty, persist the derived state onto the
  // base state.
  // 更新 updateQueue上的 state
  if (workInProgress.lanes === NoLanes) {
    // Queue is always non-null for classes
    const updateQueue: UpdateQueue<any> = (workInProgress.updateQueue: any);
    updateQueue.baseState = memoizedState;
  }
}

可以看到,在 applyDerivedStateFromProps 函数中, 执行 getDerivedStateFromProps 生命周期函数获取新的 state,然后将新的state和旧的state进行合并,将合并后的state更新到workInProgress上和updateQueue上。

checkShouldComponentUpdate

function checkShouldComponentUpdate(
  workInProgress,
  ctor,
  oldProps,
  newProps,
  oldState,
  newState,
  nextContext,
) {
  const instance = workInProgress.stateNode;
  // 执行 shouldComponentUpdate 生命周期函数,将执行结果赋值个 shouldUpdate 变量
  if (typeof instance.shouldComponentUpdate === 'function') {
    let shouldUpdate = instance.shouldComponentUpdate(
      newProps,
      newState,
      nextContext,
    );
    
    // ...
    
    // 返回 shouldComponentUpdate() 执行的结果
    return shouldUpdate;
  }

  // 判断开发中的 class组件是否是 PureComponent
  // PureComponent 中以浅层对比 prop 和 state 的方式来实现了shouldComponentUpdate 函数
  if (ctor.prototype && ctor.prototype.isPureReactComponent) {
    return (
      !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
    );
  }

  return true;
}

checkShouldComponentUpdate 函数做的事情很简单:

  1. 在开发中,如果调用了 shouldComponentUpdate 生命周期函数,则执行该函数并返回执行后的结果,即true 或 false。
  2. 如果在开发中用的是纯组件(PureComponent),则调用 shallowEqual 函数来浅比较 props/state,然后返回浅比较后的结果,也就是 true 或 false。

传送门:React PureComponent 中的浅比较

流程图

总结

本文是 React 源码解读之class组件更新updateClassComponent 系列的第三篇,介绍了ClassComponent的三种更新情形中的第二种。ClassComponent实例已经存在,但current(当前渲染在界面上的fiber树) 为null,即 ClassComponent 是初次渲染,此时调用 resumeMountClassInstance 方法,复用ClassComponent实例,并更新 state/props,然后会执行 componentWillMount 生命周期函数。

下一篇文章《React 源码解读之class组件更新updateClassComponent (四)》将介绍ClassComponent的三种更新情形中的第三种。欢迎前往阅读。