博客:pionpill
官方文档: react.dev/reference/r…
只有副作用仅需执行一次,且依赖完整的首屏渲染 DOM 时才考虑使用这个钩子。
具体实现
mountLayoutEffect
上源码(✨约2556行):
function mountLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
let fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;
return mountEffectImpl(fiberFlags, HookLayout, create, deps);
}
和 mountEffect 不同的是,传递给 mountEffectImpl 的前两个参数不同。
updateLayoutEffect
上源码(✨约2570行):
function updateLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
}
和 updateEffect 的不同也是前两个参数不同。
rerender 阶段使用的 useLayoutEffect 实现也是 updateLayoutEffect。后文如果不单独说明,默认 rerender 和 update 阶段使用同一种实现
对比一下,useEffect 与 useLayoutEffect:
Flags:useEffect打上的是PassiveEffect,useLayoutEffect是UpdateEffectHookFlags:useEffect打上的是HookPassive,useLayoutEffect是HookLayout
打上的标签不同,这两个钩子的执行时机必然也不一样。
执行时机
通过前面几篇文章我们知道 useEffect 产生的被动副作用会在浏览器有空的时候执行,是一个低优先级的异步任务。
但 useLayoutEffect 不同,它会产生一个同步任务,且有固定的执行顺序,在 commitLayoutEffects 方法中执行(commit 的最后阶段,DOM 挂载后),在该方法中有这样一段代码(✨约1041行):
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);
// 如果有 Update 标签
if (flags & Update) {
commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);
}
break;
}
具体执行顺序参考 commit-概述,函数调用栈参考 commit-DOM挂在后,我们直接看一下执行逻辑(✨约619行):
// commitHookLayoutEffects 方法调用
function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
// Mount 阶段
const create = effect.create;
const inst = effect.inst;
const destroy = create();
inst.destroy = destroy;
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
逻辑和 setState 类似,但是只会在 mount 阶段调用 create 方法执行副作用函数。
比较这两个副作用钩子:
useEffect: 异步执行,低优先级,在render和commit阶段都有可能执行useLayoutEffect: 同步执行,只在mount的layout阶段(commit阶段最后一段)执行一次。
一般首选 useEffect 钩子,它的性能更好,不会造成阻塞。如果只执行一次且需要依赖完整的 DOM 才考虑使用 useLayoutEffect。