来看看 vue3.0 跟 React16 + HOOK的源码对比 哪个香?(2)

1,813 阅读3分钟

书接上文


effect也就是在React中我们常说的side effect,在React中类似像componentDidMount这样的生命周期方法中,因为可能会执行setState这样的方法而产生新的更新,我们称为side effect即替代。本身FunctionalComponent因为是pure function,所以不会产生任何的异常,而useEffectuseLayoutEffect则是带给产生FunctionalComponent交替能力的挂钩,他们的行为非常类似componentDidMountcomponentDidUpdate

他们接受一个方法作为参数,该方法会在每次渲染完成后被调用;其次还接受第二个参数,是一个数组,这个数组里的每一个内容都会被进行进行前后的对比,如果没有变化,则不会引发该中断。


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阶段被执行。这里我们需要注意的是useLayoutEffecuseEffect增加的effectTag是不一样的,所以他们执行的时机也是不一样的。effectTag
会有以下几种情况:

  • useLayoutEffect
    增加
    UpdateEffect
  • useEffect
    增加
    UpdateEffect | PassiveEffect

以上是增加在Fiber对象上的,而记录对应的挂钩对象的effectTag
如下:

  • useLayoutEffect
    增加
    UnmountMutation | MountLayout
  • useEffect
    增加
    UnmountPassive | MountPassive
  • 如果
    areHookInputsEqual
    符合,则增加
    NoHookEffect

commit阶段Hook相关的内容

在以下三个阶段都会调用commitHookEffectList方法,我们来看一下:

  • commitWorkcommitHookEffectList(UnmountMutation, MountMutation, finishedWork);
  • commitBeforeMutationLifeCyclescommitHookEffectList(UnmountSnapshot, NoHookEffect, finishedWork);
  • commitLifeCyclescommitHookEffectList(UnmountLayout, MountLayout, finishedWork);

commitHookEffectList这个方法的内容就是根据传入的unmountTagmountTag来判断是否需要执行对应的destorycreate方法,这是在每个Hook对象的effect链上的。所以看这部分代码最重要的其实就是看他传入的effectTag和Hook对象上的effectTag的对比。

对比结果就是:

  1. useLayoutEffectdestory会在commitWork的时候被执行;而他的create会在commitLifeCycles的时候被执行。
  2. useEffect在这个流程中都不会被执行。

可以看出来useLayoutEffect的执行过程跟componentDidMountcomponentDidUpdate非常相似,所以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类似,这里我们不再重复讲解。

但我们看到这里就清楚了,useEffectdestorycreate都是异步调用的,所以他们不会影响本次更新的提交,所以不会因为在effect中产生了新的更新而导致阻塞DOM渲染的情况。

那么commitPassiveEffects做了啥呢?

export function commitPassiveHookEffects(finishedWork: Fiber): void {
  commitHookEffectList(UnmountPassive, NoHookEffect, finishedWork);
  commitHookEffectList(NoHookEffect, MountPassive, finishedWork);
}

正好对应了useEffect设置的sideEffect