likes
comments
collection
share

React Hook源码笔记(四):副作用钩子-useEffectuseEffect 用于创建副作用,副作用既会存储在

作者站长头像
站长
· 阅读数 19

博客:pionpill 官方文档: react.dev/reference/r…

mountEffect

直接看源码(✨约2456行):

function mountEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  mountEffectImpl(
    PassiveEffect | PassiveStaticEffect,
    HookPassive,
    create,
    deps,
  );
}

这里处理两种副作用:

  • PassiveEffect: 常规副作用, 例如状态更新
  • PassiveStaticEffect: 只在 mount 阶段执行一次的副作用

源码(✨约2405行):

function mountEffectImpl(
  fiberFlags: Flags,
  hookFlags: HookFlags,
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  currentlyRenderingFiber.flags |= fiberFlags;
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    createEffectInstance(), // return {destroy: undefined}
    nextDeps,
  );
}

pushEffect

源码(✨约2276行):

function pushEffect(
  tag: HookFlags,
  create: () => (() => void) | void,
  inst: EffectInstance,
  deps: Array<mixed> | null,
): Effect {
  const effect: Effect = {
    tag,  // 打上的标签
    create, // 执行逻辑
    inst, // {destroy: undefined}
    deps, // 依赖数组
    next: (null: any),
  };
  // 拿 FiberNode 的更新队列
  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 {
      // effect 添加到链表尾
      const firstEffect = lastEffect.next;
      lastEffect.next = effect;
      effect.next = firstEffect;
      componentUpdateQueue.lastEffect = effect;
    }
  }
  return effect;
}

这两个函数的逻辑还是很好理解的, pushEffect 创建了一个 Effect 对象, 同时更新 FiberNode.updateQueuelastEffect 属性(注意 effect 是会放到对应 FiberNodeupdateQueue 中), 这允许我们直接通过 FiberNode 访问到副作用。最后将 Effect 返回给 hook.memorizedState 属性.

涉及到两个新的数据结构, 一个是 Effect✨约214行):

type EffectInstance = {
  destroy: void | (() => void),
};

export type Effect = {
  tag: HookFlags, // 打上的标签
  create: () => (() => void) | void, // 开发者定义的副作用函数
  inst: EffectInstance, // 一个对象,里面有 destroy 方法用于卸载时清除副作用
  deps: Array<mixed> | null,  // 依赖数组
  next: Effect, // 指向下一个副作用
};

还有一个 FunctionComponentUpdateQueue✨约1021行):

createFunctionComponentUpdateQueue = () => {
  return {
    lastEffect: null, // 最后一个副作用, 是一个链表
    events: null, // 组件内部的事件监听器
    stores: null, // 组件内部的状态
    memoCache: null,  // 开启 memo 后才有
  };
};

那么我们可以通过 FiberNode.updateQueueHook.memorizedState 访问到副作用对象.

updateEffect

看源码(✨约2422行):

function updateEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  // 这里不处理 PassiveStaticEffect
  updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function updateEffectImpl(
  fiberFlags: Flags,
  hookFlags: HookFlags,
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const effect: Effect = hook.memoizedState;
  const inst = effect.inst;
  if (currentHook !== null && nextDeps !== null) {
    const prevEffect: Effect = currentHook.memoizedState;
    const prevDeps = prevEffect.deps;
    // 前后依赖数组是否相同
    if (areHookInputsEqual(nextDeps, prevDeps)) {
      hook.memoizedState = pushEffect(hookFlags, create, inst, nextDeps);
      return;
    }
  }

  currentlyRenderingFiber.flags |= fiberFlags;

  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    inst,
    nextDeps,
  );
}

这边的逻辑也很简单,判断运行过程中依赖数组是否有变化,如果有,则 pushEffect 多打上一个 HookHasEffect 标签,否则只打上 hookFlags 标签。

看一下比较 deps 的方法(✨约427行):

function areHookInputsEqual(
  nextDeps: Array<mixed>,
  prevDeps: Array<mixed> | null,
): boolean {
  if (prevDeps === null) {
    return false;
  }
  for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
    if (is(nextDeps[i], prevDeps[i])) {
      continue;
    }
    return false;
  }
  return true;
}

rerender 阶段使用的 useEffect 实现也是 updateEffect。

执行副作用

副作用的具体执行是在 flushPassiveEffects 方法中(参考 冲刷副作用,可以回去再看一遍),这个方法主要在 commit 阶段开始时执行,是一个低优先级任务。(参考 commit-概述)。

我们直接看一下 effect 的具体执行逻辑(之前讲过,这里再讲一遍),执行副作用方法commitHookEffectListMount✨约3363行):

function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
  // 注意这里用的是函数组件
  const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
  const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  if (lastEffect !== null) {
    const firstEffect = lastEffect.next;
    let effect = firstEffect;
    do {
      // 这里判断哪些 flag 要执行
      if ((effect.tag & flags) === flags) {
        const create = effect.create;
        const inst = effect.inst;
        // 执行副作用
        const destroy = create();
        // 返回 destroy,方便卸载时清理
        inst.destroy = destroy;
      }
      effect = effect.next;
    } while (effect !== firstEffect);
  }
}

这里有一个注意的地方,const destroy = create(),也即我们在 useEffect 钩子中副作用响应函数的返回值就是卸载时需要执行的逻辑。

卸载副作用方法: commitHookEffectListUnmount✨约567行):

function commitHookEffectListUnmount(
  flags: HookFlags,
  finishedWork: Fiber,
  nearestMountedAncestor: Fiber | null,
) {
  const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
  const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  if (lastEffect !== null) {
    const firstEffect = lastEffect.next;
    let effect = firstEffect;
    // 遍历所有副作用
    do {
      // 这里判断哪些 flag 要执行
      if ((effect.tag & flags) === flags) {
        const inst = effect.inst;
        // 拿到卸载副作用的方法
        const destroy = inst.destroy;
        if (destroy !== undefined) {
          inst.destroy = undefined;
          // 执行 destroy()
          safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
        }
      }
      effect = effect.next;
    } while (effect !== firstEffect);
  }
}
转载自:https://juejin.cn/post/7406169544089305123
评论
请登录