这是我参与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生命周期函数。欢迎前往阅读。