react函数组件生命周期函数

366 阅读4分钟

主要分析[[react]]类组件中useEffect``useLayoutEffect的实现逻辑

useEffect

render阶段

和其他hook函数一样,useEffect在挂载节点和更新阶段会调用不同的实现函数。

mountEffect

  function mountEffect(
    create: () => (() => void) | void,
    deps: Array[] | void
  ) {
    return mountEffectImpl(
      PassiveEffect | PassiveStaticEffect,
      HookPassive,
      create,
      deps
    )
  }
  function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
    // 新建一个hook对象
    const hook = mountWorkInProgressHook();
    const nextDeps = deps === undefined ? null : deps;
    // 当前fiber需要设置flags,作用是什么?
    currentlyRenderingFiber.flags |= fiberFlags;
    // 更新hook的memoizedState属性,这个属性的作用是什么?
    hook.memoizedState = pushEffect(
      // 这里的值就是 HookHasEffect | PassiveEffect
      HookHasEffect | hookFlags,   
      create,
      undefined,
      nextDeps
    )
  }
  // 目标就是将effect对象挂在到当前fiber的更新队列上
  function pushEffect(tag, create, destory, deps) {
    // 回调函数,依赖都包装成effect对象
    const effect = {
      tag,
      create,
      destory,
      deps,
      next
    }
    // 获取当前fiber的updateQueue
    let componentUpdateQueue = currentlyRenderingFiber.updateQueue;
    if (componentUpdateQueue === null) {
      // 没有更新队列,effect就是唯一的新元素
      componentUpdateQueue = createFunctionComponentUpdateQueue();
      currentlyRenderingFiber.updateQueue = componentUpdateQueue;
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      // 存在队列
      const lastEffect = componentUpdateQueue.lastEffect;
      if (lastEffect == null) {
        // 更新队列空,effect就是唯一的新元素
        componentUpdateQueue.lastEffect = effect.next = effect;
      } else {
        // 就是把新建的effect连成最后一个,新建的effect.next继续指向第一个
        const firstEffect = lastEffect.next;
        lastEffect.next = effect;
        effect.next = firstEffect;
        componentUpdateQueue.lastEffect = effect;
      }
    }
    return effect;
  }
  function createFunctionComponentUpdateQueue() {
    return {
      lastEffect: null,
      stores: null
    }
  }
  
  // 当只调用一次useEffect时,updateQueue是这样的:
  [updateQueue]--lastEffect--+
                             |
                             V
                    +---->[effect]--next--+
                    |                     |
                    +---------------------+
  
  // 当调用2次useEffect时,最终updateQueue是这样的:
  [updateQueue]--lastEffect------------------+
                                             |
                                             V
               +---->[effect]--next------>[effect]--next--+
               |                                          |
               +------------------------------------------+
  

小结

  • 每个mountEffect都会创建一个effect对象
  • 创建的effect保存到当前hook对象的memoizedState
  • 创建的effect对象函数组件fiber的updateQueue的lastEffect永远指向最后一个effect对象

updateEffect

  function updateEffect(create, deps) {
    // 比创建的时候少了PassiveStaticEffect
    return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
  }
  function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
    // 获取当前fiber对应的hook对象
    const hook = updateWorkInProgressHook();
    const nextDeps = deps === undefined ? null : deps;
    let destory = null;
    if (currentHook !== null) {
      const prevEffect = currentHook.memoizedState;
      destory = prevEffect.destory;
      // 判断是否设置依赖项,即effect的第二个参数
      if (nextDeps !== null) {
        const prevDeps = prevEffect.deps;
        // 新旧依赖会做一次浅比较
        if (areHookInputsEqual(nextDeps, prevDeps)) {
          // 新旧依赖是一致的,不需要执行create
          hook.memoizedState = pushEffect(
            hookFlags,  // hookFlags是 PassiveEffect
            create,
            destory,
            nextDeps
          );
          return;
        }
      }
    }
    // 没有旧依赖,或者新旧依赖是不一致的,需要执行create
    currentlyRenderingFiber.flags |= fiberFlags;
    hook.memoizedState = pushEffect(
      HookHasEffect | hookFlags,  // hookFlags是 HookHasEffet | PassiveEffect
      create,
      destory,
      nextDeps
    )
  }

小结

  • 每个updateEffect的同样会创建一个effect对象
  • 创建effect对象同样会关联到当前hook对象
  • 创建的effect对应会串联到当前fiber对象的updateQueue中
  • 注意是否按effect是否需要在更新的时候执行分别设置了不同的hookEffet

commit阶段

  function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
    const updateQueue = finishedWork.updateQueue;
    const lastEffect = updateQueue !== null ? updateQueue.lastEffet : null;
    if (lastEffect !== null) {
      const firstEffect = lastEffect.next;
      let effect = firstEffect;
      do {
        // 只有effect满足特定的flags,才会执行create函数
        if ((effect.flags & flags) === flags) {
          // 执行回调函数
          const create = effect.create;
          // 保存清理函数
          effect.destory = create();
        }
        
        // 遍历所有的effect对象
        effect = effect.next;
      } while(effect !== firstEffect)
    }
  }
  // 那么可以看下调用时时传递的flags:
  function commitPassiveMountOnFiber(
    finishedRoot: FiberRoot,
    finishedWork: Fiber,
  ) {
    switch(finishedWork.tag) {
      case FunctionConponent:
        // 入参是HookPassive | HookHasEffect
        // 初次渲染、更新渲染需要执行effect、更新渲染不需要执行effect时hook.flags
        // 初次渲染:              HookHasEffect | PassiveEffect
        // 更新需要执行effect:     HookHasEffect | PassiveEffect
        // 更新不需要执行effect:   PassiveEffect
        commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork)
    }
  }

小结

  • 不管是初次渲染还是更新渲染,useEffect都会创建对应的effect对象,并串联到fiber的updataQueue上
  • effect对象具备flag属性,在render阶段设置不同的值,随后在commit只有对flag是HookPassive | HookHasEffect的effect才会执行

useLayoutEffect

  function mountLayouteEffect(
    create: () => (() => void) | void,
    deps: Array<mixed>
  ) {
    let fiberFlags = UpdateEffect;
    // 和mountEffect实现一样
    return mountEffectImpl(fiberFlags, HookLayout, create, deps);
  }
  function updateLayouEffect(
    create: () => (() => void) | void,
    deps: Array<mixed>
  ) {
    // 同updateEffect实现一样
    return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
  }

发现在render阶段,useLayoutEffectuseEffect的实现是一样的,区别在于第二个参数,useEffect使用HookPassiveuseLayoutEffect使用HookLayout

  function commitLayoutEffectOnFiber(
    finishedRoot: FiberRoot,
    current: Fiber,
    workInProgress: Fiber
  ) {
    switch(finishedWork.tag) {
      case FunctionComponent: {
        commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
      }
    }
  }

在commit阶段也是一样的实现,将入参设置为HookLayout | HookHasEffect即可以了。

小结

useLayoutEffect的实现和useEffect可以说是一样的:render阶段创建effect对象,commit阶段遍effect列表,仅执行具有特定flag的effect对象。

useEffect是异步的

上面总结的useEffectuseLayoutEffect的共同点,但2个接口有一个明显的区别:useEffect是异步的,useLayoutEffect是同步的。

  function commitRootImpl(root: FiberRoot) {
    const finishedWork = root.finishedWork;
    // 如果当前fiber节点或子树置PassiveMask,说明存在useEffect
    if (finishedWork.subtreeFlags & PassiveMask !== NoFlags ||
      finishedWork.flags  PassiveMask !== NoFlags
    ) {
      if (!rootDoesHavePassiveEffects) {
        rootDoesHavePassiveEffects = true;
        // 让scheduler模块在适当的时候执行回调
        sheduleCallback(NormalSchedulerPriority, () => {
          // 最终会调用commitPassiveMountOnFiber,执行所有的useEffect回调
          flushPassiveEffects();
          return null;
        })
      }
    }
  }

在commit阶段,在真正的执行commit之前会判断是否需要执行useEffect,所以有的话则在回调函数中统一执行,所以useEffect是异步的。