在useEffect内部,会创建一个对象结构,通过这个结构可以看出,useEffect内部也是通过链表进行组织的
const effect: Effect = {
tag, //hook的类型
create, //useEffect第一个参数
destroy,//useEffect第一个参数执行的返回结果
deps, //依赖数组
// Circular
next: (null: any), //链表指针
};
这个对象最终会被放到hook所在的fiber对象的updateQueue属性上
let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
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;
}
}
updateQueue也是一个环状链表,通过环状链表可以实现快速插入节点
这样在commit阶段,就可以通过fiber.updateQueue.lastEffect就能拿到最后一个hook,再通过最后一个hook.next拿到第一个hook,从前往后执行
注:所有的hook最终都挂载在fiber的memoizedState属性上,hook对象的next指向下一个hook,hook对象上也有一个memoizedState属性,这个属性根据不同的hook来决定,例如useState的话memoizedState就会具体的值,useEffect hook对象的memoizedState就为effect对象(上面的effect对象)
重新渲染时,就算依赖数组没有发生变化,也会被放到fiber.updateQueue中
function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy = undefined;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
//依赖数组没有发生变化时,生成的effect对象的tag上没有HookHasEffect
hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
return;
}
}
}
//当依赖数组发生变化,给对应的节点打上标记
currentlyRenderingFiber.flags |= fiberFlags;
//依赖数组发生变化:effect的tag上带有HookHasEffect标记
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
destroy,
nextDeps,
);
}
当依赖数组没有发生变化时,fiber上不会有PassiveFlag的标识,只有当fiber上的flags包含PassiveFlag才会执行这个fiber上的effect
effect的最终执行在react的commit阶段,通过PassiveFlag,react可以知道哪些fiber需要执行effect,但是react是如何确定本次渲染具体哪些effect需要执行呢?
我们直接看最终执行effect的源码。以下代码只保留关键逻辑
commitHookEffectListMount就是最终执行effect的主要方法,当调用这个方法时,会传入一个flags,通过这个flags去决定哪些effect需要执行,哪些不需要执行。再结合上面updateEffectImpl方法,当依赖数组变化了effect对象上的tag带有HookHasEffect标记,反之就不带这个标记,只有有HookHasEffect标记的effect对象最终才会被执行
function commitPassiveMountOnFiber(
finishedRoot: FiberRoot,
finishedWork: Fiber,
committedLanes: Lanes,
){
commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);
}
function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
const updateQueue =finishedWork.updateQueue;
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;
effect.destroy = create();
effect = effect.next;
} while (effect !== firstEffect);
}
}