useEffect原理及源码解析

61 阅读2分钟

在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);
  }
}