Appearance
该阶段代码在 mutation
完成(DOM 修改完成)之后执行
注意:由于 JS 的同步执行阻塞了主线程,所以此时 JS 已经可以获取到新的
DOM
,但是浏览器对新的DOM
并没有完成渲染。
该阶段触发的生命周期钩子和
hook
可以直接访问到已经改变后的DOM
,即该阶段是可以参与DOM layout
的阶段。
commitLayoutEffects
js
// ReactFiberWorkLoop.js
// commitRootImpl 函数内调用
function commitRootImpl() {
// 上面是mutation 阶段
// ...同样是通过遍历 effectList
// 此时已经完成新 Fiber 树的渲染,修改FiberRootNode 的 current 指针,指向新的树
root.current = finishedWork
// layout 阶段 会调用 componentDidMount/ Update 生命周期,用到新的 Fiber
nextEffect = firstEffect
do {
commitLayoutEffects(root, lanes);
} while (nextEffect !== null)
nextEffect = null
}
// commitLayoutEffects
function commitLayoutEffects(root, lanes) {
while (nextEffect !== null) {
const flags = nextEffect.flags
// 调用生命周期钩子和hook
if (flags & (Update | Callback)) {
const current = nextEffect.alternate;
commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
}
// 赋值 ref
if (flags & Ref) {
commitAttachRef(nextEffect)
}
nextEffect = nextEffect.nextEffect
}
}
类组件
js
// ReactFiberCommitWork.js
// 引入别名 commitLayoutEffectOnFiber
function commitLifeCycles(
finishedRoot, current, finishedWork, committedLanes
) {
switch (finishedWork.tag) {
// 根据 Fiber的 tag 执行
case ClassComponent: {
// 类组件
const instance = finishedWork.stateNode
if (finishedWork.flags & Update) {
// 触发 生命周期钩子
if (current === null) {
instance.componentDidMount();
} else {
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate
);
}
}
const updateQueue = finishedWork.updateQueue
if (updateQueue !== null) {
// setState 的 第二个参数(回调函数在此执行)
commitUpdateQueue(finishedWork, updateQueue, instance);
}
return
}
}
}
函数组件
调用
useLayoutEffect hook
的回调函数
,调度useEffect
的销毁
与回调
函数
js
function commitLifeCycles() {
switch (finishedWork.tag) {
// 函数组件相关
case FunctionComponent:{
// useLayoutHook 回调
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork)
// useEffect 回调的调度
schedulePassiveEffects(finishedWork);
return
}
}
}
commitHookEffectListMount
js
// ReactFiberCommitWork.js
function commitHookEffectListMount(tag, finishedWork) {
// tag:传入的HookLayout,表示执行的是 useLayoutHook
// finishedWork:App Fiber节点
const updateQueue = finishedWork.updateQueue
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null
if (lastEffect !== null) {
const firstEffect = lastEffect.next
let effect = firstEffect
do {
if (effect.tag & tag) {
const create = effect.create
effect.destroy = create()
// layoutEffect 返回的回调函数,作为destroy
// destroy 在 mutation 阶段先调用,然后在 layout 阶段调用 create
// destroy 和 create 都是同步调用
}
} while(effect !== firstEffect)
}
}
schedulePassiveEffects
js
function schedulePassiveEffects(finishedWork) {
const updateQueue = (finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
const {next, tag} = effect;
if (
(tag & HookPassive) !== NoHookEffect &&
(tag & HookHasEffect) !== NoHookEffect
) {
// 推进调度队列中
enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
enqueuePendingPassiveHookEffectMount(finishedWork, effect);
}
effect = next;
} while (effect !== firstEffect);
}
}
function enqueuePendingPassiveHookEffectUnmount(fiber, effect) {
pendingPassiveHookEffectsUnmount.push(effect, fiber)
// rootDoesHavePassiveEffects 用于标记本次更新中存在useEffect 回调
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true
scheduleCallback(NormalSchedulerPriority, () => {
// 调度回调函数
// 内部主要是遍历 之前的 effect 销毁(destroy)和 新的 create 回调
flushPassiveEffects();
return null;
})
}
}
执行完 commitLayoutEffects
js
function commitRootImpl() {
//...commitLayoutEffects(root, lanes);
nextEffect = null;
// 至此 commit 三个子阶段完成
// 上面推入调度队列过程中赋值为 true
if (rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
}
}
flushPassiveEffects
js
// 内部调用
function flushPassiveEffectsImpl(){
// 该函数执行了 useEffect 的 destroy 回调 和 本次更新的 create 回调
// 上面赋值了为 root
if (rootWithPendingPassiveEffects === null) {return false;}
const unmountEffects = pendingPassiveHookEffectsUnmount;
// 遍历执行 unmountEffects 中的 destroy
const mountEffects = pendingPassiveHookEffectsMount;
// 遍历执行 create
}
注意点
useLayoutEffect
是 在commit阶段中同步执行 的,如果在其 create 回调中产生新的 setState ,由于同步执行(第一次 setState
,回调,回调中再次 setState
,会再次执行 useLayout
回调)不会在屏幕上显示 中间值
useEffect
是 commit 阶段之后异步执行,是可能会显示