useEffect
effect也就是在React中我们常说的side effect,在React中类似像componentDidMount这样的生命周期方法中,因为可能会执行setState这样的方法而产生新的更新,我们称之为side effect即副作用。本身FunctionalComponent因为是pure function,所以不会产生任何的副作用,而useEffect和useLayoutEffect则是带给FunctionalComponent产生副作用能力的Hooks,他们的行为非常类似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;
}
不难发现这个过程其实就是往当前Fiber上增加一系列effectTag,并且会创建updateQueue,这跟HostComponent类似,这个queue会在commit阶段被执行。这里我们需要注意的是useLayoutEffect和useEffect增加的effectTag是不一样的,所以他们执行的时机也是不一样的。effectTag会有以下几种情况:
useLayoutEffect增加UpdateEffectuseEffect增加UpdateEffect | PassiveEffect
以上是增加在Fiber对象上的,而记录对应Hook对象的effectTag如下:
useLayoutEffect增加UnmountMutation | MountLayoutuseEffect增加UnmountPassive | MountPassive- 如果
areHookInputsEqual符合,则增加NoHookEffect
记住这些内容,我们去看commit阶段做了什么跟Hook有关的内容
export function useLayoutEffect(
create: () => mixed,
inputs: Array<mixed> | void | null,
): void {
useEffectImpl(UpdateEffect, UnmountMutation | MountLayout, create, inputs);
}
export function useEffect(
create: () => mixed,
inputs: Array<mixed> | void | null,
): void {
useEffectImpl(
UpdateEffect | PassiveEffect,
UnmountPassive | MountPassive,
create,
inputs,
);
}
function useEffectImpl(fiberEffectTag, hookEffectTag, create, inputs): void {
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();
let nextInputs = inputs !== undefined && inputs !== null ? inputs : [create];
let destroy = null;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (areHookInputsEqual(nextInputs, prevEffect.inputs)) {
pushEffect(NoHookEffect, create, destroy, nextInputs);
return;
}
}
currentlyRenderingFiber.effectTag |= fiberEffectTag;
workInProgressHook.memoizedState = pushEffect(
hookEffectTag,
create,
destroy,
nextInputs,
);
}
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。
function commitHookEffectList(
unmountTag: number,
mountTag: number,
finishedWork: Fiber,
) {
if (!enableHooks) {
return;
}
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
let lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & unmountTag) !== NoHookEffect) {
// Unmount
const destroy = effect.destroy;
effect.destroy = null;
if (destroy !== null) {
destroy();
}
}
if ((effect.tag & mountTag) !== NoHookEffect) {
// Mount
const create = effect.create;
let destroy = create();
if (typeof destroy !== 'function') {
destroy = null;
}
effect.destroy = destroy;
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
results matching ""
No results matching ""
扫码添加Jokcy,更多更新更优质的前端学习内容不断更新中,期待与你一起成长!