深入理解Vue2和Vue3生命周期的变与不变

变与不变的生命周期

生命周期是每个Vue应用都需要关注的一个重要范畴。随着Vue框架从2.x版本升级到3.x版本,生命周期也进行了一些调整优化。本文将带您深入分析Vue2和Vue3在生命周期设计上的不同之处,以及其不变的内在运行机制。

vue2生命周期图示

vue2中8个生命周期函数:

  1. beforeCreate

在实例初始化之后,数据观测(data observer)和事件配置(event/watcher)之前被调用。

  1. created

在实例创建完成后被立即调用。在这一步,实例已完成以下配置:数据观测(data observer),属性和方法的运算,watch/event 事件回调。但是挂载阶段还没开始,$el 不存在。

  1. beforeMount

在实例创建完成后被立即调用。在这一步,实例已完成以下配置:数据观测(data observer),属性和方法的运算,watch/event 事件回调。但是挂载阶段还没开始,$el 不存在。

  1. mounted

el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。可以在这里操作 DOM。

  1. beforeUpdate

数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。这里适合在更新之前访问现有的 DOM。

  1. updated

由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。

  1. beforeDestroy

实例销毁之前调用。在这一步,实例仍然完全可用。

  1. destroyed

Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

vue3 生命周期图示

vue3中10个生命周期函数:

  1. setup

创建设置阶段的初始化函数,早于组件实例的任何操作。

  1. onBeforeMount

挂载开始之前调用,可以访问到声明周期的数据。

  1. onMounted

挂载后立即调用,可以访问到DOM元素和进行额外操作。

  1. onBeforeUpdate

更新前调用,访问现有DOM。

  1. onUpdated

更新后调用,在 virtural DOM 打补丁后调用。

  1. onBeforeUnmount

销毁前调用,实例仍然可以访问。

  1. onUnmounted

销毁后调用,实例已完全解绑和清理。

  1. onErrorCaptured

子组件错误捕获时调用。

  1. onRenderTracked(新增)

它会在虚拟DOM打补丁前被调用,即在进行依赖追踪和重新渲染前。 这提供了一种机会来更高效地记录或响应何时渲染会被触发。

  1. onRenderTriggered(新增)

它会在虚拟DOM重新渲染后立即被调用。 这提供了一种方式来高效地执行类似动画的操作,因为它确保渲染正在进行中。

onRenderTracke、onRenderTriggered这两个钩子的主要作用是:

  1. 追踪何时重新渲染会被触发。
  2. 响应渲染何时发生,以进行高效操作。

例如:

  • onRenderTracked可以追踪哪些依赖触发了渲染
  • onRenderTriggered可以在渲染结束后做动画过渡

它们与Vue2中的生命周期不同,更侧重于渲染本身,为开发提供了更细粒度的控制能力。

这对一些需求重绘的组件很有帮助,如大数据表格或复杂动画场景。相比之下,Vue2缺乏这类精细控制渲染的能力。

vue3和vue2的生命周期对应关系:

  • setup()对应created()
  • setup()在beforeCreate()之前执行
  • onMounted()对应mounted()
  • onUpdated()对应updated()
  • onUnmounted()对应destroyed()
  • onBeforeUnmount()对应beforeDestroy()
  • onRenderTracked()能跟踪更新的相应钩子为activated和deactivated
  • onRenderTriggered()暂未在Vue2中
  • onErrorCaptured()对应errorCaptured
  • onServerPrefetch()对应serverPrefetch

vue3源码中如何实现

js 复制代码
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)
}

除去onErrorCaptured其他的钩子函数都是直接调用createHook。

createHook方法源码解析

createHook对injectHook进行封装。

js 复制代码
 const createHook = function(lifecycle)  {
  return function (hook, target = currentInstance) {
    injectHook(lifecycle, hook, target)
  }
}

injectHook方法源码解析

js 复制代码
function injectHook(type, hook, target = currentInstance, prepend = false) {
  // 从目标组件实例中获取相应的钩子函数数组
  const hooks = target[type] || (target[type] = [])
  // 封装传入的钩子函数,用于添加实例检查、错误处理等逻辑
  const wrappedHook = hook.__weh || 
    (hook.__weh = (...args) => {
      // 检查目标组件是否已卸载
      if (target.isUnmounted) {
        return
      }
      // 停止依赖收集,避免执行钩子时产生副作用
      pauseTracking()
      // 设置当前运行组件实例为目标实例  
      setCurrentInstance(target)
      // 调用传入的钩子函数
      const res = callWithAsyncErrorHandling(hook, target, type, args)
      // 重置当前组件实例
      setCurrentInstance(null)
      // 恢复依赖收集
      resetTracking()
      return res
    })
  // 根据传入的参数将封装后的钩子推入数组前或后
  if (prepend) {
    hooks.unshift(wrappedHook) 
  } else {
    hooks.push(wrappedHook)
  }
}

这样的设计非常易于理解,因为生命周期的钩子函数在组件的不同阶段执行,所以这些钩子函数需要保存在当前组件实例上。这样,后续就可以通过不同的字符串键查找对应的钩子函数数组,并执行相应的逻辑。

举例onBeforeMount 和 onMounted

onBeforeMount 注册的 beforeMount 钩子函数会在组件挂载之前执行,onMounted 注册的 mounted钩子函数会在组件挂载之后执行。

js 复制代码
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
  // 创建响应式的副作用渲染函数
  instance.update = effect(function componentEffect() { 
    if (!instance.isMounted) {
      // 获取组件注册的钩子函数
      const { bm, m } = instance;
      // 渲染组件生成子树vnode
      const subTree = (instance.subTree = renderComponentRoot(instance))
      // 执行beforeMount钩子函数
      if (bm) {
        invokeArrayFns(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 {
      // 更新组件
    }
  }, prodEffectOptions)
} 

在执行组件挂载之前,会检测组件实例上是否存在注册的 beforeMount 钩子函数(bm)。如果存在,通过遍历 instance.bm数组并使用 invokeArrayFns 方法依次执行这些钩子函数。这样设计的原因是用户可以通过多次调用 onBeforeMount函数来注册多个 beforeMount 钩子函数,保证它们按注册顺序依次执行。

小结:

  • setup()早于任何其他钩子调用
  • onMounted()在el被新创建的实例管理后调用
  • onUpdated()在数据更新导致重新渲染后调用
  • onUnmounted()实例移出DOM且事件解绑后调用
  • onBeforeUnmount()实例即将被移除前调用
  • onErrorCaptured()捕获子组件错误时调用
  • onRenderTracked()/onRenderTriggered()跟渲染有关,Vue2无对应
  • created()被setup()替代
相关推荐
陈随易1 小时前
农村程序员-关于小孩教育的思考
前端·后端·程序员
云深时现月1 小时前
jenkins使用cli发行uni-app到h5
前端·uni-app·jenkins
昨天今天明天好多天1 小时前
【Node.js]
前端·node.js
2401_857610031 小时前
深入探索React合成事件(SyntheticEvent):跨浏览器的事件处理利器
前端·javascript·react.js
雾散声声慢2 小时前
前端开发中怎么把链接转为二维码并展示?
前端
熊的猫2 小时前
DOM 规范 — MutationObserver 接口
前端·javascript·chrome·webpack·前端框架·node.js·ecmascript
天农学子2 小时前
Easyui ComboBox 数据加载完成之后过滤数据
前端·javascript·easyui
mez_Blog2 小时前
Vue之插槽(slot)
前端·javascript·vue.js·前端框架·插槽
爱睡D小猪2 小时前
vue文本高亮处理
前端·javascript·vue.js
开心工作室_kaic2 小时前
ssm102“魅力”繁峙宣传网站的设计与实现+vue(论文+源码)_kaic
前端·javascript·vue.js