这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战」。
react 版本:v17.0.3
在任务调度流程的执行任务回调阶段,会调用 ReactFiberBeginWork.new.js 中的 beginWork 函数,根据 workInProgress 的 tag 属性,执行不同类型的React元素的更新函数。
当 workInProgress.tag 的类型为 ClassComponent 时,调用 updateClassComponent 函数对 class组件执行更新操作:
// react-reconciler/src/ReactFiberBeginWork.new.js
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
// ...
switch (workInProgress.tag) {
// ...
// 调用 updateClassComponent函数 对 class组件进行更新
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,
);
}
// ...
}
// ...
}
下面,我们来看看 updateClassComponent 这个函数做了什么事情。
updateClassComponent
// react-reconciler/src/ReactFiberBeginWork.new.js
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
// ...
// stateNode 用于记录当前 fiber 所对应的真实 dom 节点或者当前虚拟组件的实例,
// 这么做的原因第一是为了实现 Ref ,第二是为了实现真实 dom 的跟踪
// 如果是class组件,fiber的 stateNode属性 存储的是 class组件实例
// 如果是原生dom标签,fiber 的 stateNode属性 存储的 dom节点
const instance = workInProgress.stateNode;
// 用于标识组件是否需要更新
let shouldUpdate;
// 情形一:
// 组件实例还未创建
if (instance === null) {
if (current !== null) {
// A class component without an instance only mounts if it suspended
// inside a non-concurrent tree, in an inconsistent state. We want to
// treat it like a new mount, even though an empty version of it already
// committed. Disconnect the alternate pointers.
current.alternate = null;
workInProgress.alternate = null;
// Since this is conceptually a new fiber, schedule a Placement effect
workInProgress.flags |= Placement;
}
// In the initial pass we might need to construct the instance.
// 构造 class组件实例
constructClassInstance(workInProgress, Component, nextProps);
// 挂载 class组件
mountClassInstance(workInProgress, Component, nextProps, renderLanes);
// shouldUpdate 用于标记组件是否需要更新
// 由于组件是是首次构建,但还未渲染,因此设置为true,标记组件需要更新渲染
shouldUpdate = true;
} 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,
);
} else {
// 情形三:
// class组件实例已经创建并且不是初次渲染,
// 会执行 componentWillUpdate 生命周期函数,返回 shouldUpdate
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderLanes,
);
}
// 判断是否执行 render 生命周期函数,
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderLanes,
);
// ...
return nextUnitOfWork;
}
对于 ClassComponent 的更新,有三种情形:
-
情形一:ClassComponent实例未被创建,此时会调用 constructClassInstance 方法构建class组件实例,然后调用 mountClassInstance 方法挂载class组件,并将 shouldUpdate 置为 true,标记组件需要更新渲染。
-
情形二:ClassComponent实例已经存在,但current(当前渲染在界面上的fiber树) 为null,即 ClassComponent 是初次渲染,此时调用 resumeMountClassInstance 方法,复用ClassComponent实例,并更新 state/props,然后会执行 componentWillMount 生命周期函数。
-
情形三:ClassComponent实例已经存在,且已经是多次渲染,此时调用 updateClassInstance 方法执行更新操作,且会执行 componentWillUpdate 生命周期函数。
在以上三种情形中,除了情形一需要强制将 shouldUpdate 置为 true 外,其余的两种情形都是根据执行的结果来设置 shouldUpdate 的值,从而标记组件是否需要更新。
在最后,会执行 finishClassComponent 方法,判断是否需要执行 render 生命周期函数,然后返回下一个需要需要协调的fiber节点,继续执行任务调度流程。
ClassComponent 更新流程
总结
本文介绍了对class组件执行更新的函数updateClassComponent,在该函数中,对class组件的更新分为了三种情形,后续单独对这三种情形做详细解读。