这是我参与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 函数做的事情很简单:
- 在开发中,如果调用了 shouldComponentUpdate 生命周期函数,则执行该函数并返回执行后的结果,即true 或 false。
- 如果在开发中用的是纯组件(PureComponent),则调用 shallowEqual 函数来浅比较 props/state,然后返回浅比较后的结果,也就是 true 或 false。
流程图
总结
本文是 React 源码解读之class组件更新updateClassComponent 系列的第三篇,介绍了ClassComponent的三种更新情形中的第二种。ClassComponent实例已经存在,但current(当前渲染在界面上的fiber树) 为null,即 ClassComponent 是初次渲染,此时调用 resumeMountClassInstance 方法,复用ClassComponent实例,并更新 state/props,然后会执行 componentWillMount 生命周期函数。
下一篇文章《React 源码解读之class组件更新updateClassComponent (四)》将介绍ClassComponent的三种更新情形中的第三种。欢迎前往阅读。