commit阶段
commit分三个阶段: before mutation阶段,mutation阶段,layout阶段
来分别看下三个阶段做了什么事情:
Before Mutation
该阶段会执行 commitBeforeMutationEffects 函数,整体做了2件事情,
-
DOM节点删除以后的autoFous, blur的逻辑
-
调用生命周期getSnapshotBeforeUpdate生命周期钩子
-
异步调用useEffect (v18 没看到调用)
在React16以后,componetWillxxx钩子前面加了UNSAFE_前缀,原因是:这些钩子在render阶段,而Stack Reconciler 重构为Fiber Reconciler以后,render阶段是可以被中断的,在其的生命周期钩子可能会被执行多次。
所以react 提供了代替的生命周期钩子,getSnapshotBeforeUpdate。因为其在commit阶段,并commit 不会被中断,所以只会执行一次。
commitBeforeMutationEffects() {
...
}
...
instance.getSnapshotBeforeUpdate()
...
问题: 每一个setState 都会触发 从根节点到叶子节点的render,commiit 2个过程吗
问题: useEffect是如何执行的,为什么没有注入依赖就拿不到改数据?
Mutation
Mutation主要执行commitMutationEffects函数:
function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
// 遍历effectList
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
// 根据 ContentReset effectTag重置文字节点
if (effectTag & ContentReset) {
commitResetTextContent(nextEffect);
}
// 更新ref
if (effectTag & Ref) {
const current = nextEffect.alternate;
if (current !== null) {
commitDetachRef(current);
}
}
// 根据 effectTag 分别处理
const primaryEffectTag =
effectTag & (Placement | Update | Deletion | Hydrating);
switch (primaryEffectTag) {
// 插入DOM
case Placement: {
commitPlacement(nextEffect);
nextEffect.effectTag &= ~Placement;
break;
}
// 插入DOM 并 更新DOM
case PlacementAndUpdate: {
// 插入
commitPlacement(nextEffect);
nextEffect.effectTag &= ~Placement;
// 更新
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
// SSR
case Hydrating: {
nextEffect.effectTag &= ~Hydrating;
break;
}
// SSR
case HydratingAndUpdate: {
nextEffect.effectTag &= ~Hydrating;
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
// 更新DOM
case Update: {
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
// 删除DOM
case Deletion: {
commitDeletion(root, nextEffect, renderPriorityLevel);
break;
}
}
nextEffect = nextEffect.nextEffect;
}
}
可以看到主要是一个while循环,循环包括effectTag的effectList。
首先判断ContentReset,表示是否要重置文本节点
接下来是判断是否有ref 的更新
再接下来就是该阶段最重要的dom操作。
删除dom阶段会执行componentwillUnmount钩子
layout
layout阶段也是遍历effectList,总共做了如下几件事情;
1. 改变root.current指向。 把内存中的fiber树变为当前屏幕渲染的节点树。原因:componetWillUnmount是在该阶段前,访问的还是以前的dom实例,而componetDidMount是在该改变之后,在该生命周期访问的是新的dom实例。
2. 执行 commitLayoutEffectOnFiber方法。
做了如下操作:执行遍历所有的useLayoutEffects,并且执行其回调函数。 值得注意的是在Mutation阶段会先执行所有useLayoutEffects的销毁函数,然后在Layout阶段会执行所有的回调函数,且这个所有过程是同步的。
3. 判断是否有Passive,即是否存在useEffect的回调函数。如果存在,则会异步调用flushPassiveEffects方法。flushPassiveEffects:遍历执行useEffect的回调函数。 注意⚠️:由于我们的useEffect的回调函数可能产生新的更新,所以回去循环执行flshPassiveEffects直到rootWithPendingPassiveEffects为null。
总结useEffect和useLayoutEffect的区别:useLayoutEffect会在Mutation阶段执行其销毁函数,在Layout阶段执行其回调函数,整个过程为同步过程。useEffect会异步调用flushPassiveEffects,在Layout阶段注册销毁和回调函数,在commit阶段完成后执行,整个过程为异步。