
前言
今天我们来看下Commit
最后子阶段「layout
」的源码:
//=============第三个 while 循环==========================
do {
if (__DEV__) {
invokeGuardedCallback(
null,
commitLayoutEffects,
null,
root,
expirationTime,
);
//删除了 dev 代码
} else {
try {
//commit lifecycles,也就是触发生命周期的 api
//① 循环 effect 链,针对不同的 fiber 类型,进行effect.destroy()/componentDidMount()/callback/node.focus()等操作
//② 指定 ref 的引用
commitLayoutEffects(root, expirationTime);
} catch (error) {
invariant(nextEffect !== null, 'Should be working on an effect.');
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
}
} while (nextEffect !== null);
一、commitLayoutEffects()
作用:
① 循环effect
链,针对不同的fiber
类型,进行effect.destroy()/create()
/componentDidMount()
/callback
/node.focus()
等操作
② 指定 ref 的引用
源码:
function commitLayoutEffects(
root: FiberRoot,
committedExpirationTime: ExpirationTime,
) {
// TODO: Should probably move the bulk of this function to commitWork.
//循环 effect 链
while (nextEffect !== null) {
//dev 环境代码,不看
setCurrentDebugFiberInDEV(nextEffect);
const effectTag = nextEffect.effectTag;
//如果有 Update、Callback 的 effectTag 的话
if (effectTag & (Update | Callback)) {
recordEffect();
const current = nextEffect.alternate;
//重点看 FunctionComponent/ClassComponent/HostComponent
//① FunctionComponent——执行effect.destroy()/effect.create()
//② ClassComponent——componentDidMount()/componentDidUpdate(),effect 链——执行 setState 的 callback,capturedEffect 链执行 componentDidCatch
//③ HostComponent——判断是否是自动聚焦的 DOM 标签,是的话则调用 node.focus() 获取焦点
commitLayoutEffectOnFiber(
root,
current,
nextEffect,
committedExpirationTime,
);
}
//指定 ref 的引用
if (effectTag & Ref) {
recordEffect();
//获取 instance 实例,并指定给 ref
commitAttachRef(nextEffect);
}
//副作用
if (effectTag & Passive) {
rootDoesHavePassiveEffects = true;
}
//dev 环境,不看
resetCurrentDebugFiberInDEV();
nextEffect = nextEffect.nextEffect;
}
}
解析:
循环 effect 链,执行:
(1) 当有Update
/Callback
的effectTag
的话,执行commitLayoutEffectOnFiber()
,对不同的fiber
,进行effect.destroy()
/componentDidMount()
/callback
/node.focus()
等操作
(2) 当有Ref
的effectTag
的话,执行commitAttachRef()
,获取fiber
的instance
实例,并指定给ref
(3) 当有Passive
的effectTag
的话,表示有副作用,将rootDoesHavePassiveEffects
标记为true
,方便在子阶段「layout」后,再去清除副作用:
//判断本次 commit 是否会产生新的更新,也就是脏作用
const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
//如果有脏作用的处理
if (rootDoesHavePassiveEffects) {
// This commit has passive effects. Stash a reference to them. But don't
// schedule a callback until after flushing layout work.
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
pendingPassiveEffectsExpirationTime = expirationTime;
}
具体的流程,请看:
React源码解析之commitRoot整体流程概览
接下来,我们看下commitLayoutEffectOnFiber()
和commitAttachRef()
二、commitLayoutEffectOnFiber()
作用:
重点看FunctionComponent
/ClassComponent
/HostComponent
① FunctionComponent——执行effect.destroy()
/effect.create()
② ClassComponent——componentDidMount()
/componentDidUpdate()
,effect
链——执行setState
的callback
,capturedEffect
链执行componentDidCatch()
③ HostComponent——判断是否是自动聚焦的 DOM 标签,是的话则调用 node.focus() 获取焦点
源码:
function commitLifeCycles(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedExpirationTime: ExpirationTime,
): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
//循环 FunctionComponent 上的 effect 链,执行 effect.destroy()/create(),类似于 componentWillUnmount()/componentDidMount()
commitHookEffectList(UnmountLayout, MountLayout, finishedWork);
break;
}
case ClassComponent: {
const instance = finishedWork.stateNode;
//有 update 的 effectTag 的话
if (finishedWork.effectTag & Update) {
//如果是第一次渲染的话,则执行 componentDidMount()
if (current === null) {
startPhaseTimer(finishedWork, 'componentDidMount');
// 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 代码
}
instance.componentDidMount();
stopPhaseTimer();
}
//如果是多次渲染的话,则执行 componentDidUpdate()
else {
const prevProps =
finishedWork.elementType === finishedWork.type
? current.memoizedProps
: resolveDefaultProps(finishedWork.type, current.memoizedProps);
const prevState = current.memoizedState;
startPhaseTimer(finishedWork, 'componentDidUpdate');
// 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.
//删除了 dev 代码
if (__DEV__) {
}
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
stopPhaseTimer();
}
}
const updateQueue = finishedWork.updateQueue;
//如果更新队列不为空的话
if (updateQueue !== null) {
//删除了 dev 代码
if (__DEV__) {
}
// 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.
//将 capturedUpdate 队列放到 update 队列末尾
//循环 effect 链,执行 setState 的 callback
//清除 effect 链
//循环 capturedEffect 链,执行 componentDidCatch
//清除 capturedEffect 链
commitUpdateQueue(
finishedWork,
updateQueue,
instance,
committedExpirationTime,
);
}
return;
}
//fiberRoot 节点,暂时跳过
case HostRoot: {
const updateQueue = finishedWork.updateQueue;
if (updateQueue !== null) {
let instance = null;
if (finishedWork.child !== null) {
switch (finishedWork.child.tag) {
case HostComponent:
instance = getPublicInstance(finishedWork.child.stateNode);
break;
case ClassComponent:
instance = finishedWork.child.stateNode;
break;
}
}
commitUpdateQueue(
finishedWork,
updateQueue,
instance,
committedExpirationTime,
);
}
return;
}
//DOM 标签
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
// Renderers may schedule work to be done after host components are mounted
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
// These effects should only be committed when components are first mounted,
// aka when there is no current/alternate.
//如果是第一次渲染,并且该节点需要更新的 haul,就需要判断是否是自动聚焦的 DOM 标签
if (current === null && finishedWork.effectTag & Update) {
const type = finishedWork.type;
const props = finishedWork.memoizedProps;
// 判断是否是自动聚焦的 DOM 标签
commitMount(instance, type, props, finishedWork);
}
return;
}
//文本节点,无生命周期方法
case HostText: {
// We have no life-cycles associated with text.
return;
}
case HostPortal: {
// We have no life-cycles associated with portals.
return;
}
//以下的情况也跳过
case Profiler: {
if (enableProfilerTimer) {
const onRender = finishedWork.memoizedProps.onRender;
if (enableSchedulerTracing) {
onRender(
finishedWork.memoizedProps.id,
current === null ? 'mount' : 'update',
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
getCommitTime(),
finishedRoot.memoizedInteractions,
);
} else {
onRender(
finishedWork.memoizedProps.id,
current === null ? 'mount' : 'update',
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
getCommitTime(),
);
}
}
return;
}
case SuspenseComponent:
case SuspenseListComponent:
case IncompleteClassComponent:
return;
case EventComponent: {
if (enableFlareAPI) {
mountEventComponent(finishedWork.stateNode);
}
return;
}
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.',
);
}
}
}
解析:
我们重点看下FunctionComponent
/ClassComponent
/HostComponent
这三种情况:
(1) 如果是FunctionComponent
的话,则执行commitHookEffectList()
,循环effect
链,执行 effect.destroy()
/effect.create()
,类似于componentWillUnmount()
/componentDidMount()
关于commitHookEffectList()
的讲解,请看:
React源码解析之Commit第一子阶段「before mutation」 中的「三、commitHookEffectList()
」
需要注意下传的参数——commitHookEffectList(UnmountLayout, MountLayout, finishedWork)
因为是UnmountLayout
和MountLayout
,所以effect.destroy()
/effect.create()
都会执行:
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 代码
}
}
(2) 如果是ClassComponent
的话,先分两种情况:
① 如果是第一次渲染的话,则执行componentDidMount()
② 多次渲染的话,则执行componentDidUpdate()
然后是循环更新队列updateQueue
:
③ 当更新队列updateQueue
不为空时,执行commitUpdateQueue()
,循环effect
链,执行 setState()
的callback
,循环capturedEffect
链,执行componentDidCatch()
commitUpdateQueue()
方法留在后面讲
(2) 如果是DOM标签HostComponent
的话,当第一次渲染时,执行commitMount()
,判断是否是自动聚焦的DOM
标签,是的话则调用node.focus()
获取焦点
commitMount()
源码:
export function commitMount(
domElement: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Despite the naming that might imply otherwise, this method only
// fires if there is an `Update` effect scheduled during mounting.
// This happens if `finalizeInitialChildren` returns `true` (which it
// does to implement the `autoFocus` attribute on the client). But
// there are also other cases when this might happen (such as patching
// up text content during hydration mismatch). So we'll check this again.
//判断是否是自动聚焦的 DOM 标签,是的话则调用 node.focus() 获取焦点
if (shouldAutoFocusHostComponent(type, newProps)) {
((domElement: any):
| HTMLButtonElement
| HTMLInputElement
| HTMLSelectElement
| HTMLTextAreaElement).focus();
}
}
shouldAutoFocusHostComponent()
源码:
//可以 foucus 的节点返回autoFocus的值,否则返回 false
function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
//可以 foucus 的节点返回autoFocus的值,否则返回 false
switch (type) {
case 'button':
case 'input':
case 'select':
case 'textarea':
return !!props.autoFocus;
}
return false;
}
三、commitUpdateQueue()
作用:
① 将capturedUpdate
队列放到update
队列末尾
② 循环effect
链,执行effect
上的callback
,也就是this.setState({},()=>{})
里的callback
③ 清除effect
链
④ 循环capturedEffect
链,执行componentDidCatch()
⑤ 清除capturedEffect
链
源码:
export function commitUpdateQueue<State>(
finishedWork: Fiber,
finishedQueue: UpdateQueue<State>,
instance: any,
renderExpirationTime: ExpirationTime,
): void {
// If the finished render included captured updates, and there are still
// lower priority updates left over, we need to keep the captured updates
// in the queue so that they are rebased and not dropped once we process the
// queue again at the lower priority.
//如果目标节点 render 时,捕获到了 update error,并且仍有低优先级的 update 未执行,那么 React 会在
//队列中保持这 update error,并去让低优先级的 update 去执行该 update error
//在更新时,捕获到了 error
//如果 update 队列仍存在,则将 capturedUpdate 放到正常 update 队列的末尾
//清除 capturedUpdate 链表
if (finishedQueue.firstCapturedUpdate !== null) {
// Join the captured update list to the end of the normal list.
//将 capturedUpdate 链表放到正常 update 队列的末尾
if (finishedQueue.lastUpdate !== null) {
finishedQueue.lastUpdate.next = finishedQueue.firstCapturedUpdate;
finishedQueue.lastUpdate = finishedQueue.lastCapturedUpdate;
}
// Clear the list of captured updates.
//将 capturedUpdate 放到正常 update 队列的末尾后,清除 capturedUpdate 链表
finishedQueue.firstCapturedUpdate = finishedQueue.lastCapturedUpdate = null;
}
// Commit the effects
//循环 effect 链,执行 effect 上的 callback,也就是 this.setState({},()=>{})里的 callback
commitUpdateEffects(finishedQueue.firstEffect, instance);
//清除 effect 链
finishedQueue.firstEffect = finishedQueue.lastEffect = null;
//循环 capturedEffect 链,执行 capturedEffect 上的 callback,即 componentDidCatch()
commitUpdateEffects(finishedQueue.firstCapturedEffect, instance);
//清除 capturedEffect 链
finishedQueue.firstCapturedEffect = finishedQueue.lastCapturedEffect = null;
}
解析:
代码结构和作用上的顺序一致,就不赘述了,看下commitUpdateEffects()
源码:
function commitUpdateEffects<State>(
effect: Update<State> | null,
instance: any,
): void {
while (effect !== null) {
const callback = effect.callback;
if (callback !== null) {
effect.callback = null;
//源码:callback.call(context);
//注意是用 .call() 来执行 callback 的,目的就是指定 this
callCallback(callback, instance);
}
effect = effect.nextEffect;
}
}
循环effect
链,执行effect
上的callback
注意:
① effect
链上的callback
是this.setState({},callback)
的callback
② capturedEffect
链上的callback
是componentDidCatch()
最后讲下快被遗忘的commitAttachRef()
四、commitAttachRef()
作用:
获取 instance 实例,并指定给 ref
源码:
function commitAttachRef(finishedWork: Fiber) {
const ref = finishedWork.ref;
if (ref !== null) {
const instance = finishedWork.stateNode;
let instanceToUse;
//获取可使用的 instance(实例)
switch (finishedWork.tag) {
//DOM标签
case HostComponent:
instanceToUse = getPublicInstance(instance);
break;
default:
instanceToUse = instance;
}
//指定 ref 的引用
if (typeof ref === 'function') {
ref(instanceToUse);
} else {
//删除了 dev 代码
ref.current = instanceToUse;
}
}
}
解析:
(1) 如果 ref 是 function,则执行ref(instance)
(2) 如果 ref 是 object,则执行ref.current = instance
总结
子阶段「layout」的两部分逻辑
(1) 循环effect链,针对不同的fiber类型,进行不同的操作
① FunctionComponent——执行effect.destroy()
/effect.create()
② ClassComponent——componentDidMount()
/componentDidUpdate()
,effect
链——执行setState
的callback
,capturedEffect
链执行componentDidCatch()
③ HostComponent——首次渲染判断是否是自动聚焦的 DOM 标签,是的话则调用node.focus()
获取焦点
(2) 指定ref
的引用
① ref(instance)
② ref.current = instance
Commit阶段流程图

GitHub
commitLayoutEffects()
:
https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberWorkLoop.js
commitLayoutEffectOnFiber() as commitLifeCycles()/commitAttachRef()
:
https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberCommitWork.js
commitUpdateQueue()
https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactUpdateQueue.js

(完)
网友评论