从源码层次了解 React 生命周期:更新
大家好,我是前端西瓜哥。
今天我们继续从源码层面看 React 的更新阶段,是如何触发类函数的生命周期函数的。
上一篇:
React 版本为 18.2.0。 为了更关注本文中的代码片段省略了大量的细致末节,并直接丢掉函数的参数。
上一篇文章说了挂载过程中,React 底层是如何调用类组件的生命周期函数的。这次就说说更新的情况。
还是这个图:
image-20221124135036981
这次我们讲解更新阶段。触发顺序为:
- componentWillReceiveProps(废弃,所以上图中没有,但还是要说说)
- static getDerivedStateFromProps
- shouldComponentUpdate
- componentWillUpdate(废弃,所以上图中没有,但还是要说说)
- render
- getSnapshotBeforeUpdate
- componentDidUpdate
componentWillReceiveProps
updateClassComponent 源码位置: https://github.com/facebook/react/blob/1cd90d2ccc791f3ed25d93ceb7137746185f6e34/packages/react-reconciler/src/ReactFiberClassComponent.new.js#L1093 callComponentWillReceiveProps 源码位置: https://github.com/facebook/react/blob/1cd90d2ccc791f3ed25d93ceb7137746185f6e34/packages/react-reconciler/src/ReactFiberClassComponent.new.js#L1142
componentWillReceiveProps 会在父组件更新时被调用,并拿到最新的 props 和 context,此时实例的属性还未变更。
首先调用 updateClassComponent 方法,这个方法会根据条件走向 “挂载”(mountClassInstance)或 “更新”(updateClassInstance)。下面代码的 instance 就是类组件实例,如果已经创建了实例,说明。
function updateClassComponent() {
const instance = workInProgress.stateNode;
let shouldUpdate;
if (instance === null) { // 初始化组件
mountClassInstance(workInProgress, Component, nextProps, renderLanes);
shouldUpdate = true;
} else if (current === null) { // 类组件实例存在,但 fiber 没构建的情况,会进行复用
shouldUpdate = resumeMountClassInstance(workInProgress, Component, nextProps, renderLanes);
} else { // 更新组件
shouldUpdate = updateClassInstance(current, workInProgress, Component, nextProps, renderLanes);
}
}
更新阶段,会执行 updateClassInstance 方法。
updateClassInstance 里其实会执行绝大多数的生命周期钩子,只要是在操作真实 DOM 前的都会调用。
updateClassInstance 首先会 调用 callComponentWillReceiveProps 方法:
function updateClassInstance() {
// 1. 调用 componentWillReceiveProps
if (oldProps !== newProps || oldContext !== nextContext) {
callComponentWillReceiveProps(
workInProgress,
instance,
newProps,
nextContext,
);
}
}
可以看到,必须 props 变成新的,或者 context 是新的,才会调用 componentWillReceiveProps。
callComponentWillReceiveProps 的核心代码为:
function callComponentWillReceiveProps() {
instance.componentWillReceiveProps(newProps, nextContext);
instance.UNSAFE_componentWillReceiveProps(newProps, nextContext);
}
调用栈(在 componentWillReceiveProps 上加上 console.trace('componentWillReceiveProps')
):
image-20221125213838158
static getDerivedStateFromProps
applyDerivedStateFromProps 源码地址: https://github.com/facebook/react/blob/1cd90d2ccc791f3ed25d93ceb7137746185f6e34/packages/react-reconciler/src/ReactFiberClassComponent.new.js#L163
componentWillReceiveProps 是类静态方法,会在组件更新时,拿到最新的 props 以及最新的 state,该函数的返回值会合并到 state 对象上。
需要注意的是,因为它是类静态方法,所以无法拿到 this.state 或者 this.props。
还是 updateComponentInstance,紧随着 componentWillReceiveProps 调用 applyDerivedStateFromProps,然后调用 componentWillReceiveProps 方法将返回的对象组合到类组件实例的 state 中。
function updateClassInstance() {
// 1. 调用 componentWillReceiveProps
// 2. 调用 getDerivedStateFromProps
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
newProps,
);
}
applyDerivedStateFromProps 和上一篇的挂载阶段流程一致。
function applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
nextProps,
) {
const prevState = workInProgress.memoizedState;
let partialState = getDerivedStateFromProps(nextProps, prevState);
// 合并
const memoizedState =
partialState === null || partialState === undefined
? prevState
: assign({}, prevState, partialState);
workInProgress.memoizedState = memoizedState;
}
调用栈:
image-20221125101044583
shouldComponentUpdate
checkShouldComponentUpdate 源码地址: https://github.com/facebook/react/blob/1cd90d2ccc791f3ed25d93ceb7137746185f6e34/packages/react-reconciler/src/ReactFiberClassComponent.new.js#L308
shouldComponet 会接收新 props 和 state,我们将它们与当前的 props 和 state 对比,返回一个布尔值,决定是否应用新的 props 和 state,并重渲染组件。
updateClassInstance 方法下,会调用一些方法判断是否允许更新组件,有:
- checkHasForceUpdateAfterProcessing():
this.forceUpdate()
会将一个名为 hasForceUpdate 的变量临时变成 true,checkHasForceUpdateAfterProcessing 方法会返回该变量; - checkShouldComponentUpdate():这个方法会调用类组件实例的 shouldComponentUpdate。
function updateClassInstance() {
// 1. 调用 componentWillReceiveProps
// 2. 调用 getDerivedStateFromProps
// 是否应该更新组件
// 3. 调用 shouldComponentUpdate
const shouldUpdate =
// 是否为强制更新(对应 this.forceUpdate())
checkHasForceUpdateAfterProcessing() ||
// 调用 shouldComponentUpdate
checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
) ||
// context 相关
(enableLazyContextPropagation &&
current !== null &&
current.dependencies !== null &&
checkIfContextChanged(current.dependencies));
}
checkShouldComponentUpdate 的核心代码:
function checkShouldComponentUpdate() {
const instance = workInProgress.stateNode;
let shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
nextContext,
);
return shouldUpdate;
}
调用栈:
image-20221125154311755
componentWillUpdate
紧接着前面代码中拿到的 shouldUpdate 值,如果是真值,就会执行 componentWillUpdate。
function updateClassInstance() {
// 1. 调用 componentWillReceiveProps
// 2. 调用 getDerivedStateFromProps
// 是否应该更新组件
// 3. 调用 shouldComponentUpdate
shouldUpdate = true; // 假设为 true
// 如果 shouldUpdate 为真值,
// 4. 调用 componentWillUpdate
if (shouldUpdate) {
instance.componentWillUpdate(newProps, newState, nextContext);
instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
}
instance.props = newProps;
instance.state = newState;
instance.context = nextContext;
return shouldUpdate;
}
题外话,React 还会在这里打标签,标记之后是否要执行 componentDidUpdate 和 getSnapshotBeforeUpdate 方法。
workInProgress.flags |= Update;
workInProgress.flags |= Snapshot;
调用栈:
image-20221125161822580
render
走完前面的 updateClassInstance 函数,我们拿到一个代表是否要更新组件的 shouldUpdate 布尔值。
接着我们就 调用 finishClassComponent 方法。
function updateClassComponent() {
const instance = workInProgress.stateNode;
let shouldUpdate;
if (instance === null) {
// ...
} else if (current === null) {
// ...
} else { // 更新组件
shouldUpdate = updateClassInstance(current, workInProgress, Component, nextProps, renderLanes);
}
// 调用 finishClassComponent 方法,传入了 shouldUpdate
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderLanes,
);
}
finishClassComponent 核心代码:
function finishClassComponent() {
// 如果 shouldUpdate 为假,停止更新
if (!shouldUpdate) {
// Context providers should defer to sCU for rendering
if (hasContext) {
invalidateContextProvider(workInProgress, Component, false);
}
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// shouldUpdate 为真,走正常流程
// 5. 调用 render
nextChildren = instance.render();
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
调用栈:
image-20221125162338612
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate 的执行时间是在接近更新 DOM 前,该方法会拿到更新前的 state 和 props,并会返回值缓存起来,在 componetDidUpdate 中获取。
从这个方法开始,就进入了 commit 阶段,具体是在 commitBeforeMutationEffectsOnFiber 方法下。
逻辑不复杂,首先拿到类组件实例 instance,调用它的 getSnapshotBeforeUpdate 方法,传入 prevProps 和 prevState。
然后将返回的值保存到 instance.__reactInternalSnapshotBeforeUpdate 下,准备给 componetDidUpdate 用。
function commitBeforeMutationEffectsOnFiber(finishedWork) {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
const instance = finishedWork.stateNode;
switch (finishedWork.tag) {
case ClassComponent: {
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState,
);
// 保存到类组件实例的 __reactInternalSnapshotBeforeUpdate 上
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
break;
}
}
}
调用栈:
image-20221125172551556
componentDidUpdate
componentDidUpdate 会在 DOM 更新后被调用,接受三个参数:
- prevProps:上一个 props;
- prevState:上一个 state;
- snapshot:前面提到的那个 snapshot;
具体是在 commitLayoutEffectOnFiber 方法内,调用 componentDidUpdate 方法。
function commitLayoutEffectOnFiber(finishedRoot, current, finishedWork, committedLanes) {
switch (finishedWork.tag) {
case ClassComponent: {
if (current === null) {
// 挂载
instance.componentDidMount();
} else {
// 更新
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
}
break;
}
}
}
调用栈:
image-20221125173934467
图示
按照惯例,画个简单的流程图。
image-20221125212312197
结尾
我是前端西瓜哥,欢迎关注我,学习更多前端知识。
相关文章
- 数据结构(java版)学习笔记(四)——线性表之循环链表
- 数据结构(java版)学习笔记(三)——线性表之单链表
- 数据结构(java版)学习笔记(二)——线性表之顺序表
- 数据结构(java版)学习笔记(一)——线性表
- 数据结构(java版)学习笔记(序章)
- 抓 https 加密数据,偷偷摸摸爽得很!
- 小游戏赛道迎来新一轮增长机会,技术升级实现多平台布局
- Photoshop 2021正式版更新,附全系列下载
- Ps | Adobe Photoshop 2023 for Win 24.1 中文激活版下载及安装教程
- 简单易用的监控告警系统 | HertzBeat 在 Rainbond 上的使用分享
- 原来Docker容器中设置时区这么简单
- 小游戏进入增长快车道,行业变现模式分析
- (一)汇编语言——基础知识
- ghost系统后只有C盘了别的盘的文件怎样恢复
- (二)汇编语言——寄存器
- (三)汇编语言——DOSBox
- 分布式是大数据处理的万能药?
- SAP ABAP 报表屏幕输入字段如何实现联动效果试读版
- 如何给 SAP ABAP ALV 报表的修改功能添加自定义校验逻辑试读版
- 参加 Spartacus 开源项目开发时需要注意的一些编程规范