Vue3 源码解读系列(八)——生命周期

生命周期

在这里插入图片描述

正常的生命周期

// 注册钩子函数
const onBeforeMount = createHook('bm'/* BEFORE_MOUNT */)
const onMounted = createHook('m'/* MOUNTED */)
const onBeforeUpdate = createHook('bu'/* BEFORE_UPDATE */)
const onUpdated = createHook('u'/* UPDATED */)
const onBeforeUnmount = createHook('bum'/* BEFORE_UNMOUNT */)
const onUnmounted = createHook('um'/* UNMOUNTED */)
const onRenderTriggered = createHook('rtg'/* RENDER_TRIGGERED */)
const onRenderTracked = createHook('rtc'/* RENDER_TRACKED */)
const onErrorCaptured = (hook, target = currentInstance) => {
  injectHook('ec'/* ERROR_CAPTURED */, hook, target)
}

/**
 * 创建钩子函数
 * createHook 只是对 injectHook 的封装,区别只有第一个参数不同
 */
function createHook(lifecycle) {
  return function (hook, target = currentInstance) {
    // 通过 injectHook 注册钩子函数
    injectHook(lifecycle, hook, target)
  }
}

/**
 * 注册钩子函数
 * injectHook 主要是对用户注册的 hook 进行封装,然后添加到一个数组中,把数组保存到当前组件实例 target 上
 * 钩子函数必须要保存在当前的组件实例上,通过不同的字符串 key 找到对应的钩子函数数组并执行
 */
function injectHook(type, hook, target = currentInstance, prepend = false) {
  const hooks = target[type] || (target[type] = [])

  // 封装 hook 钩子函数并缓存
  const wrappedHook = hook.__weh || (hook.__weh = (...args) => {
    if (target.isUnmounted) return

    // 停止依赖收集(因为执行生命周期时往往已经执行过依赖收集了,所以不需要在执行生命周期时继续依赖收集,这会损耗性能)
    pauseTracking()

    // 在钩子函数执行的时候,为了确保此时的 currentInstance 和注册钩子函数是一致的,会通过 setCurrentInstance 设置 target 为当前的组件实例
    setCurrentInstance(target)

    // 执行钩子函数
    const res = callWithAsyncErrorHandling(hook, target, type, args)
    setCurrnetInstance(null)

    // 恢复依赖收集
    resetTracking()
    return res
  })
  if (prepend) {
    hooks.unshift(wrappedHook)
  } else {
    hook.push(wrappedHook)
  }
}

/**
 * 组件副作用渲染函数
 */
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
  // 创建响应式的副作用渲染函数
  instance.update = effect(function componentEffect() {
    // 组件挂载部分
    // 父 beforeMount -> 子 beforeMount -> 子 mounted -> 父 mounted
    if (!instance.isMounted) {
      // 获取组件实例上通过 onBeforeMount 钩子函数和 onMounted 注册的钩子函数
      const { bm, m } = instance

      // 渲染组件生成子树 vnode
      const subTree = (instance.subTree = renderComponentRoot(instance))

      // 执行 beforeMount 钩子函数
      if (bm) {
        // bm 是一个数组,因为用户可以通过指定多个 onBeforeMount 函数注册多个钩子函数,因此这里是遍历数组依次执行
        invokeArrayFus(bm)
      }

      // 把子树 vnode 挂载到 container 中
      patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)

      // 保留渲染生成的子树根 DOM 节点
      initialVNode.el = subTree.el

      // 执行 mounted 钩子函数
      if (m) {
        queuePostRenderEffect(m, parentSuspense)
      }
      instance.isMounted = true
    }
    // 组件更新部分
    // 组件的更新只涉及当前组件
    else {
      // 获取组件实例上通过 onBeforeUpdate 钩子函数和 onUpdated 注册的钩子函数
      let { next, vnode, bu, u } = instance

      // next 表示新的组件 vnode
      if (next) {
        // 更新组件 vnode 节点信息
        updateComponentPreRender(instance, next, optimized)
      } else {
        next = vnode
      }

      // 渲染新的子树 vnode
      const nextTree = renderComponentRoot(instance)

      // 缓存旧的子树 vnode
      const prevTree = instance.subTree

      // 更新子树 vnode
      instance.subTree = nextTREE

      // 执行 beforeUpdate 钩子函数
      if (bu) {
        invokeArrayFns(bu)
      }

      // 组件更新核心逻辑,根据新旧子树 vnode 做 patch
      patch(prevTree, nextTree,
        // 如果在 teleport 组件中父节点可能已经改变,所以容器直接找旧树 DOM 元素的父节点
        hostParentNode(prevTree.el),
        // 缓存更新后的 DOM 节点
        getNextHostNode(prevTree), instance, parentSuspense, isSVG)

      // 缓存更新后的 DOM 节点
      next.el = nextTree.el

      // 执行 updated 钩子函数
      // 注意:不要在 updated 钩子函数中更改数据,因为会再次触发组件更新!!!
      if (u) {
        queuePostRenderEffect(u, parentSuspense)
      }
    }
  }, prodEffectOptions)
}

/**
 * 组件副作用渲染函数 - 组件销毁部分
 * 父 beforeUnmount -> 子 beforeUnmount -> 子 unmounted -> 父 unmounted
 */
const unmountComponent = (instance, parentSuspense, doRemove) => {
  const { bum, effects, update, subTree, um } = instance

  // 执行 beforeUnmount 钩子函数
  if (bum) {
    invokeArrayFns(bum)
  }

  // 清理组件引用的 effects 副作用函数
  if (effects) {
    for (let i = 0; i < effects.length; i++) {
      stop(effects[i])
    }
  }

  // 如果一个异步组件在加载前就销毁了,则不会注册副作用渲染函数
  if (update) {
    stop(update)

    // 调用 unmount 销毁子树
    unmount(subTree, instance, parentSuspense, doRemove)
  }

  // 执行 unmounted 钩子函数,通过递归的方式遍历子树销毁子节点
  if (um) {
    queuePostRenderEffect(um, parentSuspense)
  }
}

捕获后代组件错误的生命周期

/**
 * 捕获后代组件的错误 - onErrorCaptured
 */
function handleError(err, instance, type) {
  const contextVNode = instance ? instance.vnode : null
  if (instance) {
    let cur = instance.parent
    const exposedInstance = instance.proxy // 为了兼容 2.x 版本,暴露组件实例给钩子函数

    // 获取错误信息
    const errorInfo = (process.env.NODE_ENV !== 'production') ? ErrorTypeStrings[type] : type

    // 尝试向上查找所有父组件,执行 errorCaptured 钩子函数
    while (cur) {
      const errorCapturedHooks = cur.ec
      // 如果存在则遍历执行
      if (errorCapturedHooks) {
        for (let i = 0; i < errorCapturedHooks.length; i++) {
          // 判断 errorCaptured 钩子函数返回 true,则停止向上查找
          if (errorCapturedHooks[i](err, exposedInstance, errorInfo)) return
        }
      }
      cur = cur.parent
    }
  }
  // 如果整个链路上都没有能够正确处理错误的 errCaptured 钩子函数,则控制台输出未处理的错误
  logError(err, type, contextVNode)
}

开发环境下调试的生命周期

/**
 * 创建开发环境的副作用函数的配置
 * 实际上是在 track、trigger 阶段添加副作用函数并执行
 */
function createDevEffectOptions(instance) {
  return {
    scheduler: queueJob,
    onTrack: instance.rtc ? e => invokeArrayFns(instance.rtc, e) : void 0,
    onTrigger: instance.rtg ? e => invokeArrayFns(instance.rtg, e) : void 0
  }
}

/**
 * track 的实现
 */
function track(target, type, key) {
  // 执行一些依赖收集的操作
  // ...

  // 执行完依赖收集后执行 onTrack 函数,遍历执行 renderTracked 钩子函数
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect)
    activeEffect.deps.push(dep)
    // 在非生产环境下检测当前 activeEffect 的配置有没有定义 onTrack 函数,如果有,则执行该方法
    if ((process.env.NODE_ENV !== 'production') && activeEffect.options.onTrack) {
      // 执行 onTrack 函数
      activeEffect.options.onTrack({
        effect: activeEffect,
        target,
        type,
        key
      })
    }
  }
}

/**
 * trigger 的实现
 */
function trigger(target, type, key, newValue) {
  // 添加要运行的 effects 集合,然后遍历执行
  // ...

  // 在非生产环境下检测待执行的 effect 配置是否定义 onTrigger 函数,如果有,则执行该方法
  const run = (effect) => {
    if ((process.env.NODE_ENV !== 'production') && effect.options.onTrigger) {
      // 执行 onTrigger
      effect.options.onTrigger({
        effect,
        target,
        key,
        type,
        newValue,
        oldValue,
        oldTarget
      })
    }

    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }

  // 遍历执行 effects
  effects.forEach(run)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jackson Mseven

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值