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

230 阅读9分钟

这是我参与2022首次更文挑战的第13天,活动详情查看: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

// ClassComponent 的第三种更新情形
else {
  // class组件实例已经创建并且不是初次渲染,
  // 会执行 componentWillUpdate 生命周期函数,返回 shouldUpdate
  shouldUpdate = updateClassInstance(
    current,
    workInProgress,
    Component,
    nextProps,
    renderLanes,
  );
}

情形三:ClassComponent实例已经存在,且已经是多次渲染

在 ClassComponent 更新的第三种情形中,会调用 updateClassInstance 方法执行更新操作,并且会执行 componentWillUpdate 生命周期函数。

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

updateClassInstance

updateClassInstance函数的作用:执行 componentWillUpdate 生命周期函数,返回 shouldUpdate

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

function updateClassInstance(
  current: Fiber,
  workInProgress: Fiber,
  ctor: any,
  newProps: any,
  renderLanes: Lanes,
): boolean {
  // 从 fiber 节点上获取 class组件实例 
  const instance = workInProgress.stateNode;

  // 从 current 树上拷贝 updateQueue 添加到 workInProgress 上
  cloneUpdateQueue(current, workInProgress);

  // 从 workInProgress 上获取旧的 props
  const unresolvedOldProps = workInProgress.memoizedProps;
  const oldProps =
    workInProgress.type === workInProgress.elementType
      ? unresolvedOldProps
      : resolveDefaultProps(workInProgress.type, unresolvedOldProps);

  // 更新 class组件实例上的props
  instance.props = oldProps;
  // 待更新的props   
  const unresolvedNewProps = workInProgress.pendingProps;

  // 获取 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 nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
  }

  // 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 (
      unresolvedOldProps !== unresolvedNewProps ||
      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 (
    unresolvedOldProps === unresolvedNewProps &&
    oldState === newState &&
    !hasContextChanged() &&
    !checkHasForceUpdateAfterProcessing() &&
    !(
      enableLazyContextPropagation &&
      current !== null &&
      current.dependencies !== null &&
      checkIfContextChanged(current.dependencies)
    )
  ) {
    // 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.

    // 执行 componentDidUpdate 和 getSnapshotBeforeUpdate 生命周期函数
    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;
  }

  // 如果有调用 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,
    ) ||
    // TODO: In some cases, we'll end up checking if context has changed twice,
    // both before and after `shouldComponentUpdate` has been called. Not ideal,
    // but I'm loath to refactor this function. This only happens for memoized
    // components so it's not that common.
    (enableLazyContextPropagation &&
      current !== null &&
      current.dependencies !== null &&
      checkIfContextChanged(current.dependencies));

  // 需要更新,执行 componentWillUpdate、componentDidUpdate 和 getSnapshotBeforeUpdate
  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') {
        instance.componentWillUpdate(newProps, newState, nextContext);
      }
      if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
        instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
      }
    }
    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.

    // 安排一个更新以保证componentWillUpdate 和 componentDidUpdate 被调用
    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.

    // 即使不需要更新,也会更新原有的 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;
}

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

1、首先通过stateNode属性从workInProgress上获取class组件实例,接着调用cloneUpdateQueue拷贝current上的updateQueue,添加到workInProgress上,然后从workInProgress上获取已有的 props、context 等。代码如下:

// 从 fiber 节点上获取 class组件实例 
const instance = workInProgress.stateNode;

// 从 current 树上拷贝 updateQueue 添加到 workInProgress 上
cloneUpdateQueue(current, workInProgress);

// 从 workInProgress 上获取旧的 props
const unresolvedOldProps = workInProgress.memoizedProps;
const oldProps =
  workInProgress.type === workInProgress.elementType
    ? unresolvedOldProps
    : resolveDefaultProps(workInProgress.type, unresolvedOldProps);

// 更新 class组件实例上的props
instance.props = oldProps;
// 待更新的props   
const unresolvedNewProps = workInProgress.pendingProps;

// 获取 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 nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
  nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
}

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 (
    unresolvedOldProps !== unresolvedNewProps ||
    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 (
  unresolvedOldProps === unresolvedNewProps &&
  oldState === newState &&
  !hasContextChanged() &&
  !checkHasForceUpdateAfterProcessing() &&
  !(
    enableLazyContextPropagation &&
    current !== null &&
    current.dependencies !== null &&
    checkIfContextChanged(current.dependencies)
  )
) {
  // 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.

  // 执行 componentDidUpdate 和 getSnapshotBeforeUpdate 生命周期函数
  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;
    }
  }
  // 即 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,
  ) ||
  // TODO: In some cases, we'll end up checking if context has changed twice,
  // both before and after `shouldComponentUpdate` has been called. Not ideal,
  // but I'm loath to refactor this function. This only happens for memoized
  // components so it's not that common.
  (enableLazyContextPropagation &&
    current !== null &&
    current.dependencies !== null &&
    checkIfContextChanged(current.dependencies));

8、如果检查后的结果是需要更新,即 shouldUpdate 为true,那么组件需要更新。判断在class组件实例上是否调用了componentWillUpdate、componentDidUpdate 和 getSnapshotBeforeUpdate 生命周期函数,若有,则执行这三个生命周期函数。代码如下:

// 需要更新,执行 componentWillUpdate、componentDidUpdate 和 getSnapshotBeforeUpdate
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')
  ) {
    // 执行 componentWillUpdate 生命周期函数
    if (typeof instance.componentWillUpdate === 'function') {
      instance.componentWillUpdate(newProps, newState, nextContext);
    }
    // 执行 UNSAFE_componentWillUpdate 生命周期函数
    if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
      instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
    }
  }
  // 执行 componentDidUpdate 生命周期函数
  if (typeof instance.componentDidUpdate === 'function') {
    workInProgress.flags |= Update;
  }
  // 执行 getSnapshotBeforeUpdate 生命周期函数
  if (typeof instance.getSnapshotBeforeUpdate === 'function') {
    workInProgress.flags |= Snapshot;
  }
}

9、如果检查的结果是不需要更新,即 shouldUpdate 为 false,仍会判断执行 componentDidUpdate() 和 getSnapshotBeforeUpdate(),并更新当前工作的 fiber 对象的 memoizedProps 和 memoizedState,以便可以复用。代码如下:

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.

  // 安排一个更新以保证componentWillUpdate 和 componentDidUpdate 被调用
  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.

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

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

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

return shouldUpdate;

接下来,我们来逐个看看在 updateClassInstance 中调用的函数。其中callComponentWillReceiveProps、resetHasForceUpdateBeforeProcessing、applyDerivedStateFromProps、checkShouldComponentUpdate 这四个函数在《React 源码解读之class组件更新updateClassComponent (三)》中有介绍,此处不再赘述。

processUpdateQueue 函数在《React 源码解读之class组件更新updateClassComponent (二)》已有介绍,此处也不再赘述。

下面我们就来看看 cloneUpdateQueue 这个函数。

cloneUpdateQueue

cloneUpdateQueue的作用:从 current 树上拷贝 updateQueue 添加到 workInProgress 上。

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

export function cloneUpdateQueue<State>(
  current: Fiber,
  workInProgress: Fiber,
): void {
  // Clone the update queue from current. Unless it's already a clone.
  // 获取当前工作的 fiber 节点的updateQueue   
  const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
  
  // 获取当前渲染在屏幕上的fiber树上的 updateQueue    
  const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
  // workInProgress 的updateQueue 和 current 的updateQueue是相同的
  if (queue === currentQueue) {
    // 拷贝 current 的updateQueue
    const clone: UpdateQueue<State> = {
      baseState: currentQueue.baseState,
      firstBaseUpdate: currentQueue.firstBaseUpdate,
      lastBaseUpdate: currentQueue.lastBaseUpdate,
      shared: currentQueue.shared,
      effects: currentQueue.effects,
    };
    // 更新 workInProgress 的updateQueue为 current 的updateQueue
    workInProgress.updateQueue = clone;
  }
}

可以看到,cloneUpdateQueue 做的事情很简单,如果workInProgress 的updateQueue 和 current 的updateQueue相同,则复制 current 上的updateQueue,然后将复制后的updateQueue更新到 workInProgress 上。

流程图

下面是class组件实例调用 updateClassInstance 方法执行更新操作,且执行 componentWillUpdate 生命周期函数的流程图:

总结

本文是 React 源码解读之class组件更新updateClassComponent 系列的第四篇,介绍了ClassComponent的三种更新情形中的第三种。ClassComponent实例已经存在,且已经是多次渲染,此时调用 updateClassInstance 方法执行更新操作,且会执行 componentWillUpdate 生命周期函数。

下一篇文章《React 源码解读之class组件更新updateClassComponent (五)》将介绍ClassComponent的三种更新情形都执行完更新后,是否需要执行render生命周期函数。欢迎前往阅读。