React中useEffect与useLayoutEffect的区别
⽬录
前置知识
useEffect
commitBeforeMutationEffects
commitMutationEffects
commitLayoutEffects
后续阶段
useLayoutEffect
结论
前置知识
我们可以将 React 的⼯作流程划分为⼏⼤块:
1. render 阶段:主要⽣成 Fiber节点并构建出完整的 Fiber树
reacthooks理解
2. commit 阶段:在上⼀个render 阶段中会在 rootFiber 上⽣成⼀条副作⽤链表,应⽤的DOM操作就会在本阶段执⾏commit阶段的⼯作主要分为三部分,对应到源码中的函数名是:
commitBeforeMutationEffects阶段:主要处理执⾏DOM操作前的⼀些相关操作
commitMutationEffects阶段:执⾏DOM操作
commitLayoutEffects阶段:主要处理执⾏DOM操作后的⼀些相关操作
useEffect 和 useLayoutEffect 的区别主要就在体现在这三个阶段的处理上。结论是:useEffect 会异步地去执⾏它的响应函数和上⼀次的销毁函数,⽽useLayoutEffect 会同步地执⾏它的响应函数和上⼀次的销毁函数,即会阻塞住 DOM渲染。useEffect
commitBeforeMutationEffects
在这个阶段中 useEffect 着重会经历⼀句话如下:
function commitBeforeMutationEffects() {
while (nextEffect$1 !== null) {
// ⼀系列的赋值操作省略,这⾥的flags应取⾃对应FunctionComponent的effect的flags,具体实现请看源码
var flags = effect.flags;
// 处理⽣命周期
if ((flags & Snapshot) !== NoFlags) {
setCurrentFiber(nextEffect$1);
commitBeforeMutationLifeCycles(current, nextEffect$1);
resetCurrentFiber();
}
// 这个if判断只有 useEffect 为 true,useLayoutEffect 为false
if ((flags & Passive) !== NoFlags) {
// If there are passive effects, schedule a callback to flush at
// the earliest opportunity.
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
// 这⾥就是 useEffect 异步的原因,DOM操作后React会调度 flushPassiveEffects
scheduleCallback(NormalPriority, function () {
flushPassiveEffects();
return null;
});
}
}
nextEffect$1 = Effect;
}
}
commitMutationEffects
在这个阶段中,React 会进⾏⼀系列的DOM节点更新,然后会执⾏⼀个⽅法: commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
那么⼀个拥有 useEffect 的 Functional Component 在这个阶段是不符合 unmount 的判断逻辑的,所以在这个地⽅不会做unmount 操作。
commitLayoutEffects
在这个阶段中,依然有⼀个很重要的⽅法存在:commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
这个if判断和上⼀阶段的if判断是⼀样的,useEffec 在这个判断中不会做任何操作。
后续阶段
在完成了 commitLayoutEffects 后,还有⼀个操作:
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;
pendingPassiveEffectsLanes = lanes;
pendingPassiveEffectsRenderPriority = renderPriorityLevel;
}
即把 rootWithPendingPassiveEffects 置为 root ,这么做的原因和第⼀阶段 commitBeforeMutationEffects 中 useEffect 注册的下⼀次 flushPassiveEffects 异步调度有关,我们看以下 flushPassiveEffects 的实现:
function flushPassiveEffectsImpl() {
if (rootWithPendingPassiveEffects === null) {
return false;
}
// 省略⼀系列的性能追踪等操作
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current);
}
从上述代码段可以看见,useEffect 在第⼀阶段注册的调度回调会在页⾯更新后进⾏ unmount 和 mount
操作。值得⼀提的是,这个回调中effect的注册时机就是在 commitLayoutEffects 阶段。
useLayoutEffect
其实根据我们对 useEffect 的解析来看,就是在 commitMutationEffects 和 commitLayoutEffects 阶段中各⾃的 if 判断
中,useLayoutEffect 是通过if判断的,所以在 commitMutationEffects 阶段中,同步执⾏了useLayoutEffect 的上⼀次销毁函数,在 commitLayoutEffects 阶段中,同步执⾏了 useLayoutEffect 本次的执⾏函数,并注册上销毁函数。
结论
⾄此,我们粗略地查看了 commit 阶段的代码,分析了以下为什么 useEffect 是异步执⾏,⽽ useLayoutEffect 是同步执⾏,具体的代码我没有太过在⽂章中贴出来,因为这些都是可变的,真正的流程性的概览和 React 团队设计这⼀套机制的⼼智模型需要我们⾃⼰在不断调试代码和理解中慢慢去熟悉。
后续⾃⼰感兴趣的是 hooks 的实现,其中⽐较关键的 useReducer 会着重看⼀下源码,看看能不能写个简易版本的放到⽀付宝⼩程序中去实现⼀个⾃定义的⽀付宝hooks ⽤于⽇常⽣产⼒开发。
到此这篇关于React中useEffect 与 useLayoutEffect的区别的⽂章就介绍到这了,更多相关React useEffect useLayoutEffect内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!