前言
生命周期钩子(Lifecycle Hooks)是 Vue 组件从诞生到销毁的全过程记录。掌握生命周期,不仅能让我们在正确的时间点执行逻辑,更是优化性能、排查内存泄露的关键。
一、 生命周期四大阶段
Vue 的生命周期大体可分为:创建、挂载、更新、销毁。
二、 Vue 2 vs Vue 3 生命周期对比图
在 Vue 3 组合式 API 中,生命周期钩子需要从 vue 中导入,且命名上增加了 on 前缀。
| 阶段 | Vue 2 (选项式 API) | Vue 3 (组合式 API) | 备注 |
|---|---|---|---|
| 创建 | beforeCreate / created | setup() | Vue 3 中 setup 包含了这两个时期 |
| 挂载 | beforeMount / mounted | onBeforeMount / onMounted | 常用:操作 DOM、请求接口 |
| 更新 | beforeUpdate / updated | onBeforeUpdate / onUpdated | 响应式数据变化时触发 |
| 销毁 | beforeDestroy / destroyed | onBeforeUnmount / onUnmounted | 注意:Vue 3 中命名的变更 |
| 缓存 | activated / deactivated | onActivated / onDeactivated | 配合 <keep-alive> 使用 |
三、 详细解析与实战场景
1. 创建阶段 (Creation)
-
Vue 2 (
beforeCreate/created) :beforeCreate:组件实例刚在内存中被创建,此时还没有初始化好data和methods属性。适合插件开发,注入全局变量。created:实例已创建,响应式数据data、methods已准备好。- 场景:最早可发起异步请求的时机。
-
Vue 3 (
setup) :- 在 Vue 3 中,
setup的执行早于beforeCreate,它是组合式 API 的入口。
- 在 Vue 3 中,
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. 初始挂载阶段
父组件必须等待所有子组件挂载完成后,才能完成自己的挂载逻辑。
- 父
setup(开始创建) - 父
onBeforeMount - 子
setup - 子
onBeforeMount - 子
onMounted(子组件渲染完毕,向上通知) - 父
onMounted(父组件接收到信号,宣布整体挂载完毕)
记忆口诀: 父创 -> 子创 -> 子挂 -> 父挂。
2. 更新阶段
当父组件传递给子组件的 props 发生变化时,更新逻辑如下:
- 父
onBeforeUpdate - 子
onBeforeUpdate - 子
onUpdated - 父
onUpdated
注意: 如果只是父组件自身的私有状态更新,且未影响到子组件,则子组件的更新钩子不会被触发。
3. 销毁阶段
销毁过程同样是“递归”式的,父组件先启动销毁,等子组件销毁完毕后,父组件正式功成身退。
- 父
onBeforeUnmount - 子
onBeforeUnmount - 子
unmounted - 父
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>
📝 总结与避坑
-
接口请求放哪里?
- 如果子组件的渲染依赖父组件接口返回的数据,请在父组件的
created(Vue 2)或setup(Vue 3)中请求。 - 注意:即便你在父组件的
onMounted发请求,子组件此时也已经渲染完成了。
- 如果子组件的渲染依赖父组件接口返回的数据,请在父组件的
-
Refs 访问时机:
- 父组件想通过
ref访问子组件实例,必须在父组件的onMounted之后,因为只有这时子组件才真正挂载完成。
- 父组件想通过
-
异步组件:
- 如果子组件是异步组件(如使用
defineAsyncComponent),顺序会发生变化,父组件可能会先执行onMounted。
- 如果子组件是异步组件(如使用