Vue-从 Vue 2 到 Vue 3:生命周期全图鉴与实战指南

0 阅读5分钟

前言

生命周期钩子(Lifecycle Hooks)是 Vue 组件从诞生到销毁的全过程记录。掌握生命周期,不仅能让我们在正确的时间点执行逻辑,更是优化性能、排查内存泄露的关键。

一、 生命周期四大阶段

Vue 的生命周期大体可分为:创建、挂载、更新、销毁


二、 Vue 2 vs Vue 3 生命周期对比图

在 Vue 3 组合式 API 中,生命周期钩子需要从 vue 中导入,且命名上增加了 on 前缀。

阶段Vue 2 (选项式 API)Vue 3 (组合式 API)备注
创建beforeCreate / createdsetup()Vue 3 中 setup 包含了这两个时期
挂载beforeMount / mountedonBeforeMount / onMounted常用:操作 DOM、请求接口
更新beforeUpdate / updatedonBeforeUpdate / onUpdated响应式数据变化时触发
销毁beforeDestroy / destroyedonBeforeUnmount / onUnmounted注意:Vue 3 中命名的变更
缓存activated / deactivatedonActivated / onDeactivated配合 <keep-alive> 使用

三、 详细解析与实战场景

1. 创建阶段 (Creation)

  • Vue 2 (beforeCreate / created)

    • beforeCreate:组件实例刚在内存中被创建,此时还没有初始化好 datamethods 属性。适合插件开发,注入全局变量。
    • created:实例已创建,响应式数据data、methods 已准备好。
      • 场景:最早可发起异步请求的时机。
  • Vue 3 (setup)

    • 在 Vue 3 中,setup 的执行早于 beforeCreate,它是组合式 API 的入口。

2. 挂载阶段 (Mounting)

  • Vue 2 (beforeMount / mounted)
    • beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中

    • mounted:此时已经将编译好的模板挂载到了页面指定的容器中,可以访问页面中的dom了

      • 场景:dom已创建,可用于获取接口数据和dom元素、访问子组件

  • Vue 3 (onBeforeMount / onMounted)
    • onBeforeMount:模板编译完成,但尚未渲染到 DOM 树中。

    • onMounted:组件已挂载,可以安全地访问 DOM 元素。

      • 场景:获取接口数据、初始化第三方插件(如 ECharts)、访问子组件。

3. 更新阶段 (Updating)

  • Vue 2 (beforeUpdate / updated)

    • beforeUpdate:数据状态更新之前执行,此时 data 中的状态值是最新的,但是界面上显示的数据还是旧的,因为此时还没有开始重新渲染DOM节点。

      • 场景 :此时view层还未更新,可用于获取更新前各种状态。
    • updated:实例更新完毕之后调用,此时 data 中的状态值和界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了。

  • Vue 3 (onBeforeUpdate / onUpdated)

    • onBeforeUpdate:数据已更新,但 DOM 尚未重新渲染。可用于获取更新前的 DOM 状态。
    • onUpdated:DOM 已完成更新。注意:不要在此钩子中修改状态,否则可能导致死循环。

4. 销毁阶段 (Unmounting / Destruction)

  • Vue 2 (beforeDestroy / destroyed)

    • beforeDestroy:实例销毁之前调用。
      • 场景:清理工作,如 清除定时器 (setInterval)、解绑全局事件监听、取消订阅
    • destroyed:Vue 实例销毁后调用。组件彻底从 DOM 中移除,所有的指令和事件监听都会被解除。
  • Vue 3 (onBeforeUnmount / onUnmounted)

    • onBeforeUnmount:实例销毁之前调用。

    • onUnmounted:组件彻底从 DOM 中移除,所有的指令和事件监听都会被解除。

5. 缓存阶段 (Keep-alive)

如果使用了keep-alive缓存组件会新增两个生命周期函数

  • onActivated:组件进入视野,被重新激活时调用。
  • onDeactivated:组件移出视野,进入缓存状态时调用。

四、 Vue 3 + TypeScript 实战演示

以下是使用 script setup 语法编写的生命周期示例:

<template>
  <div ref="container">
    <h2>当前计数:{{ count }}</h2>
    <button @click="count++">增加</button>
  </div>
</template>

<script setup lang="ts">
import { 
  ref, 
  onMounted, 
  onBeforeUpdate, 
  onUpdated, 
  onBeforeUnmount 
} from 'vue'

const count = ref<number>(0)
const container = ref<HTMLElement | null>(null)
let timer: number | null = null

// 挂载阶段
onMounted(() => {
  console.log('Component Mounted. DOM element:', container.value)
  // 模拟一个定时任务
  timer = window.setInterval(() => {
    console.log('Timer running...')
  }, 1000)
})

// 运行阶段
onBeforeUpdate(() => {
  console.log('Data updated, but DOM is not yet re-rendered.')
})

onUpdated(() => {
  console.log('Data updated and DOM re-rendered.')
})

// 销毁阶段
onBeforeUnmount(() => {
  console.log('Cleanup before unmount.')
  if (timer) {
    clearInterval(timer) // 关键:防止内存泄漏
  }
})
</script>

五、 进阶:父子组件生命周期执行顺序

为了清晰起见,我们将顺序拆解为三个主要场景(vue3):

1. 初始挂载阶段

父组件必须等待所有子组件挂载完成后,才能完成自己的挂载逻辑。

  1. setup(开始创建)
  2. onBeforeMount
  3. setup
  4. onBeforeMount
  5. onMounted (子组件渲染完毕,向上通知)
  6. onMounted (父组件接收到信号,宣布整体挂载完毕)

记忆口诀: 父创 -> 子创 -> 子挂 -> 父挂。


2. 更新阶段

当父组件传递给子组件的 props 发生变化时,更新逻辑如下:

  • onBeforeUpdate
  • onBeforeUpdate
  • onUpdated
  • onUpdated

注意: 如果只是父组件自身的私有状态更新,且未影响到子组件,则子组件的更新钩子不会被触发。


3. 销毁阶段

销毁过程同样是“递归”式的,父组件先启动销毁,等子组件销毁完毕后,父组件正式功成身退。

  1. onBeforeUnmount
  2. onBeforeUnmount
  3. unmounted
  4. onUnmounted

六、 Vue 3 + TS 模拟演示

你可以通过以下代码在控制台直接观察执行逻辑。

父组件 Parent.vue

<script setup lang="ts">
import { onMounted, onBeforeMount } from 'vue'
import Child from './Child.vue'

console.log('1. 父 - setup')

onBeforeMount(() => console.log('3. 父 - onBeforeMount'))
onMounted(() => console.log('8. 父 - onMounted'))
</script>

<template>
  <div class="parent">
    <h1>父组件</h1>
    <Child />
  </div>
</template>

子组件 Child.vue

<script setup lang="ts">
import { onMounted, onBeforeMount } from 'vue'

console.log('4. 子 - setup')

onBeforeMount(() => console.log('6. 子 - onBeforeMount'))
onMounted(() => console.log('7. 子 - onMounted'))
</script>

<template>
  <div class="child">子组件内容</div>
</template>

📝 总结与避坑

  1. 接口请求放哪里?

    • 如果子组件的渲染依赖父组件接口返回的数据,请在父组件的 created(Vue 2)或 setup(Vue 3)中请求。
    • 注意:即便你在父组件的 onMounted 发请求,子组件此时也已经渲染完成了。
  2. Refs 访问时机

    • 父组件想通过 ref 访问子组件实例,必须在父组件的 onMounted 之后,因为只有这时子组件才真正挂载完成。
  3. 异步组件

    • 如果子组件是异步组件(如使用 defineAsyncComponent),顺序会发生变化,父组件可能会先执行 onMounted