文章首发于我的公众号:前端西瓜哥
大家好,我是前端西瓜哥。今天我们看看组件挂载时,React 底层是如何调用我们类组件的生命周期函数的。
React 源码使用的是 18.2.0 版本。
React 在重构的过程中,改了很多内部的方法名,如果你在旧版本的 React 源码里查找文章提及的内部方法,可能会找不到。
下面的代码都是去掉了细枝末节的,只保留和生命周期相关的逻辑。
今天来看看一个类组件在挂载的时候,react 源码层面发生了什么事情。
我们先看流程图:
本文只讲解挂载阶段。
挂载阶段源码分析
挂载(mount)阶段,依次执行的方法顺序为:
- constructor;
- static getDerivedStateFromProps;
- componentWillMount(已废弃,不建议使用。图上没写,但 componentWillMount 是在 render 前执行的);
- render;
- componentDidMount;
constructor
首先是对类组件进行实例化。
react-reconiler 包中的 constructClassInstance 方法:
function constructClassInstance(
workInProgress,
ctor,
props
) {
let instance = new ctor(props, context);
// ...
}
这个 ctor(constructor 的缩写)就是我们的类组件,也就是一个 class,我们会 new 一下它生成实例,并挂载到 fiber.stateNode 上。
我们在 componentDidMount 用 console.trace('constructor')
打印调用栈,可以得到下面结果。
getDerivedStateFromProps
getDerivedStateFromProps 是类静态方法,可以拿到最新的 props,然后将返回的对象合并到 state 上。
调用完 constructClassInstance 方法中后,紧接着就会调用一个叫做 mountClassInstance 的方法。
function mountClassInstance(
workInProgress,
ctor,
newProps,
renderLanes,
) {
// 调用类静态方法 getDerivedStateFromProps
applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
newProps,
);
// 这里还有执行 componentWillMount 逻辑,后面再说
}
mountClassInstance 下会 调用 applyDerivedStateFromProps,这个方法会调用类的 getDerivedStateFromProps 静态方法,得到 partialState 合并到 state 上。
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;
}
打印下调用栈:
componentWillMount
componentWillMount 已废弃,不建议使用,且改名为 UNSAFE_componentWillMount。
该函数只会在 getDerivedStateFromProps 和 getSnapshotBeforeUpdate 不存在时才会触发。
这个逻辑还是在 mountClassInstance 下。
function mountClassInstance(
workInProgress,
ctor,
newProps,
renderLanes,
) {
// 调用类静态方法 getDerivedStateFromProps
// ...
// 这里还有执行 componentWillMount
if (
typeof ctor.getDerivedStateFromProps !== 'function' &&
typeof instance.getSnapshotBeforeUpdate !== 'function' &&
(typeof instance.UNSAFE_componentWillMount === 'function' ||
typeof instance.componentWillMount === 'function')
) {
callComponentWillMount(workInProgress, instance);
}
}
callComponentWillMount 核心代码为:
function callComponentWillMount(workInProgress, instance) {
if (typeof instance.componentWillMount === 'function') {
instance.componentWillMount();
}
if (typeof instance.UNSAFE_componentWillMount === 'function') {
instance.UNSAFE_componentWillMount();
}
}
有意思,React 底层还是同时兼容旧的 componentWillMount 写法的,新旧两种 API 都会被调用。但会在控制台打印警告信息就是了。
打印一下调用栈:
render
接着就是 调用 finishClassComponent 方法,调用类组件实例的 render 方法,拿到 reactElement。接着调用 reconcileChildren 递归处理调和子元素。
function finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderLanes,
) {
const instance = workInProgress.stateNode;
// 调用 render 拿到
nextChildren = instance.render();
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
打印一下调用栈:
componentDidMount
componentDidMount 会在 DOM 更新后调用。
前面的方法都是在 render 阶段调用,它们是同步发生的。
而 componentDidMount 则是在 commit 阶段执行,是异步的。React 会在所有的 fiber 调和(reconcile)完后进入 commit 阶段。
具体在 commitLayoutEffectOnFiber 内调用:
function commitLayoutEffectOnFiber(
finishedRoot,
current,
finishedWork,
committedLanes,
) {
switch (finishedWork.tag) {
// ...
case ClassComponent: {
const instance = finishedWork.stateNode;
if (current === null) {
// 调用实例的 componentDidMount
instance.componentDidMount();
}
break;
}
// ...
}
}
打印下调用栈:
图示说明
画个图,更好地理解这个流程。
结尾
至此,React 中的一个类组件的挂载过程调用完所有的生命周期函数。
我是前端西瓜哥,欢迎关注我,学习更多前端知识。