前言
今天我们来看下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阶段流程图
GitHubcommitLayoutEffects()
:
github.com/AttackXiaoJ…
commitLayoutEffectOnFiber() as commitLifeCycles()/commitAttachRef()
:
github.com/AttackXiaoJ…
commitUpdateQueue()
github.com/AttackXiaoJ…
(完)