在上篇我们介绍了 commitRoot的整体流程,那么本篇就来介绍它的第一个子阶段 「before mutation
」:
do {
if (__DEV__) {
invokeGuardedCallback(null, commitBeforeMutationEffects, null);
//删除了 dev 代码
} else {
try {
//调用 classComponent 上的生命周期方法 getSnapshotBeforeUpdate
//关于getSnapshotBeforeUpdate,请看:https://zh-hans.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate
commitBeforeMutationEffects();
} catch (error) {
invariant(nextEffect !== null, 'Should be working on an effect.');
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
}
} while (nextEffect !== null);
作用:
循环effect
链,对有Snapshot
的effect
执行commitBeforeMutationEffectOnFiber
源码:
function commitBeforeMutationEffects() {
//循环 effect 链
while (nextEffect !== null) {
//如果 effectTag 里有 Snapshot 这个 effectTag 的话
//关于&,请看[前端小知识10点(2020.2.10)](https://mp.weixin.qq.com/s/tt2XcW4GF7oBBZOPwTiCcg)中的「8、JS 中的 & 是什么意思」
if ((nextEffect.effectTag & Snapshot) !== NoEffect) {
//dev 可不看
// setCurrentDebugFiberInDEV(nextEffect);
//计 effect 的数
recordEffect();
//获取当前 fiber 节点
const current = nextEffect.alternate;
commitBeforeMutationEffectOnFiber(current, nextEffect);
//dev 可不看
// resetCurrentDebugFiberInDEV();
}
nextEffect = nextEffect.nextEffect;
}
}
解析:
(1) nextEffect
是effect
链上从firstEffect
到lastEffect
的每一个需要commit
的fiber
对象
(2) 当nextEffect
上有Snapshot
这个effectTag
时,执行commitBeforeMutationEffectOnFiber()
,让不同类型的组件执行不同的操作,来提交(commit
)相关effect
作用:
(1) 如果该fiber
类型是ClassComponent
的话,执行getSnapshotBeforeUpdate
生命周期api
,将返回的值赋到fiber
对象的__reactInternalSnapshotBeforeUpdate
上
(2) 如果该fiber
类型是FunctionComponent
的话,执行hooks
上的effect
相关 API
(3) 关于&
运算符,请看:
前端小知识10点(2020.2.10) 中的 「8、JS 中的 & 是什么意思
」
源码:
function commitBeforeMutationLifeCycles(
current: Fiber | null,
finishedWork: Fiber,
): void {
switch (finishedWork.tag) {
//FunctionComponent会执行commitHookEffectList()
//FunctionComponent是 pureComponent,所以不会有副作用
//useEffect 和 useLayoutEffect 是赋予FunctionComponent有副作用能力的 hooks
//useEffect类似于componentDidMount,useLayoutEffect类似于componentDidUpdate
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
//提交 hooks 的 effects
commitHookEffectList(UnmountSnapshot, NoHookEffect, finishedWork);
return;
}
case ClassComponent: {
if (finishedWork.effectTag & Snapshot) {
if (current !== null) {
//老 props
const prevProps = current.memoizedProps;
//老 state
const prevState = current.memoizedState;
//getSnapshotBeforeUpdate 的计时开始
startPhaseTimer(finishedWork, 'getSnapshotBeforeUpdate');
//获取 classComponent 的实例
const instance = finishedWork.stateNode;
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
if (__DEV__) {
//删除了 dev 代码
}
//执行 getSnapshotBeforeUpdate 生命周期 api,在组件update前捕获一些 DOM 信息,
//返回自定义的值或 null,统称为 snapshot
//关于getSnapshotBeforeUpdate,请参考:https://zh-hans.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState,
);
if (__DEV__) {
//删除了 dev 代码
}
//将 snapshot 赋值到__reactInternalSnapshotBeforeUpdate属性上,
// 这种手法跟[React源码解析之updateClassComponent(上)](https://mp.weixin.qq.com/s/F_UdPgdt6wtP78eDqUesoA)
// 中的「三、adoptClassInstance」里 instance._reactInternalFiber=workInProgress 类似
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
//getSnapshotBeforeUpdate 的计时结束
stopPhaseTimer();
}
}
return;
}
case HostRoot:
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:
// Nothing to do for these component types
return;
//没有副作用,不应该进入到 commit 阶段
default: {
invariant(
false,
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
}
}
解析:
(1) 根据fiber
的tag
即类型,进行不同的操作。主要看FunctionComponent
和ClassComponent
这两种情况。
(2) 如果是FunctionComponent
的话,则执行commitHookEffectList()
,该方法稍后解析。
(3) 如果是ClassComponent
的话,则执行生命周期方法——getSnapshotBeforeUpdate()
,并将返回值赋予给fiber
对象上的__reactInternalSnapshotBeforeUpdate
属性上。
也就是说,类组件的子阶段 「before mutation
」 目的是:
调用getSnapshotBeforeUpdate()
,在commit
前获取 DOM 相关信息
(4) getSnapshotBeforeUpdate()
在最近一次渲染输出(提交到 DOM 节点)之前调用。
它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。
此生命周期的任何返回值将作为参数传递给componentDidUpdate()
,更多详情请参考:
https://zh-hans.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate
(5)
instance.__reactInternalSnapshotBeforeUpdate = snapshot
这种手法跟 React源码解析之updateClassComponent(上) 中的 「三、adoptClassInstance
」 里的
instance._reactInternalFiber=workInProgress
类似
说明:
useEffect
是让FunctionComponent
产生副作用的hooks
,当使用useEffect
后,会在fiber
上的updateQueue.lastEffect
生成effect
链,具体请看ReactFiberHooks.js中的pushEffect()
作用:
循环FunctionComponent
上的effect
链,并根据每个effect
上的effectTag
,执行destroy/create
操作(作用类似于componentDidMount
/componentWillUnmount
)
源码:
function commitHookEffectList(
unmountTag: number,
mountTag: number,
finishedWork: Fiber,
) {
//FunctionComponent 的更新队列
//补充:FunctionComponent的 side-effect 是放在 updateQueue.lastEffect 上的
//ReactFiberHooks.js中的pushEffect()里有说明:componentUpdateQueue.lastEffect = effect.next = effect;
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
let lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
//如果有副作用 side-effect的话,循环effect 链,根据 effectTag,执行每个 effect
if (lastEffect !== null) {
//第一个副作用
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
//如果包含 unmountTag 这个 effectTag的话,执行destroy(),并将effect.destroy置为 undefined
if ((effect.tag & unmountTag) !== NoHookEffect) {
// Unmount
const destroy = effect.destroy;
effect.destroy = undefined;
if (destroy !== undefined) {
destroy();
}
}
//如果包含 mountTag 这个 effectTag 的话,执行 create()
if ((effect.tag & mountTag) !== NoHookEffect) {
// Mount
const create = effect.create;
effect.destroy = create();
if (__DEV__) {
//删除了 dev 代码
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
解析:
(1) 不同于ClassComponent
的 effect 链直接取fiber.firstEffect
—>fiber.firstEffect.next
......,
FunctionComponent
的 effect 链取的是fiber.updateQueue.lastEffect
—>fiber.updateQueue.lastEffect. next
....
(2) 循环FunctionComponent
上的 effect 链,
如果effectTag
是unmountTag
的话,就执行effect.destroy()
方法,感觉跟componentWillUnmount()
作用类似;
如果effectTag
是mountTag
的话,就执行effect.create()
方法,感觉跟componentDidMount()
作用类似;
在 「before mutation
」 子阶段上,
ClassComponent
会执行getSnapshotBeforeUpdate()
,捕获 DOM 信息;
FunctionComponent
会执行effect.create()
/effect.destory()
,类似于componentDidMount()
/componentWillUnmount()
commitBeforeMutationEffects()
:
https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberWorkLoop.js
commitBeforeMutationLifeCycles()
/commitHookEffectList()
:
https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberCommitWork.js