书接上文
effect也就是在React中我们常说的side effect,在React中类似像componentDidMount这样的生命周期方法中,因为可能会执行setState这样的方法而产生新的更新,我们称为side effect即替代。本身FunctionalComponent因为是pure function,所以不会产生任何的异常,而useEffect和useLayoutEffect则是带给产生FunctionalComponent交替能力的挂钩,他们的行为非常类似componentDidMount和componentDidUpdate
function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {
return {
lastEffect: null,
};
}
function pushEffect(tag, create, destroy, inputs) {
const effect: Effect = {
tag,
create,
destroy,
inputs,
// Circular
next: (null: any),
};
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
useLayoutEffect增加UpdateEffectuseEffect增加UpdateEffect | PassiveEffect
useLayoutEffect增加UnmountMutation | MountLayoutuseEffect增加UnmountPassive | MountPassive- 如果
areHookInputsEqual符合,则增加NoHookEffect
commit阶段Hook相关的内容
在以下三个阶段都会调用commitHookEffectList方法,我们来看一下:
commitWork中commitHookEffectList(UnmountMutation, MountMutation, finishedWork);commitBeforeMutationLifeCycles中commitHookEffectList(UnmountSnapshot, NoHookEffect, finishedWork);commitLifeCycles中commitHookEffectList(UnmountLayout, MountLayout, finishedWork);
commitHookEffectList这个方法的内容就是根据传入的unmountTag和mountTag来判断是否需要执行对应的destory和create方法,这是在每个Hook对象的effect链上的。所以看这部分代码最重要的其实就是看他传入的effectTag和Hook对象上的effectTag的对比。
对比结果就是:
useLayoutEffect的destory会在commitWork的时候被执行;而他的create会在commitLifeCycles的时候被执行。useEffect在这个流程中都不会被执行。
可以看出来useLayoutEffect的执行过程跟componentDidMount和componentDidUpdate非常相似,所以React官方也说了,如果你一定要选择一个类似于生命周期方法的Hook,那么useLayoutEffect是不会错的那个,但是我们推荐你使用useEffect,在你清除他们的区别的前提下,后者是更好的选择。
那么useEffect什么时候被调用呢?
答案在commitRoot的最后,他等其他sideEffect全部commit完了之后,会执行以下代码:
if (
enableHooks &&
firstEffect !== null &&
rootWithPendingPassiveEffects !== null
) {
let callback = commitPassiveEffects.bind(null, root, firstEffect);
passiveEffectCallbackHandle = Schedule_scheduleCallback(callback);
passiveEffectCallback = callback;
}rootWithPendingPassiveEffects是在commitAllLifeCycles的时候如果发现更新中有passive effect的节点的话,就等于FiberRoot。
if (enableHooks && effectTag & Passive) {
rootWithPendingPassiveEffects = finishedRoot;
}这里如果有,则会发起一次Schedule_scheduleCallback,这个就是我们之前讲的异步调度模块Scheduler的方法,流程跟PerformWork类似,这里我们不再重复讲解。
但我们看到这里就清楚了,useEffect的destory和create都是异步调用的,所以他们不会影响本次更新的提交,所以不会因为在effect中产生了新的更新而导致阻塞DOM渲染的情况。
那么commitPassiveEffects做了啥呢?
export function commitPassiveHookEffects(finishedWork: Fiber): void {
commitHookEffectList(UnmountPassive, NoHookEffect, finishedWork);
commitHookEffectList(NoHookEffect, MountPassive, finishedWork);
}正好对应了useEffect设置的sideEffect。