Vue3 生命周期完全指南
前言:什么是生命周期?
想象一下一个人的一生:出生 → 成长 → 工作 → 退休 → 离世。Vue 组件也有类似的生命周期,从创建到销毁的整个过程就是生命周期。
生命周期钩子就是 Vue 在组件生命周期的不同阶段自动调用的函数,让我们有机会在特定时刻执行自己的代码。
第一章:生命周期全景图
概括来总结一下生命周期:
组件诞生
↓
beforeCreate → 组件刚初始化,什么都还没有
↓
created → 数据已初始化,但DOM还未创建
↓
beforeMount → 模板编译完成,但还未挂载到页面
↓
mounted → 组件已显示在页面中 ✅
↓
┌─→ beforeUpdate → 数据变化,DOM更新前
│ ↓
│ updated → DOM已更新完成
│ ↓
└─────┘
↓
beforeUnmount → 组件即将被销毁
↓
unmounted → 组件已销毁 🏁
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
在这个过程中,Vue 会在不同阶段自动触发一些“钩子函数”(生命周期钩子),我们就可以在这些关键节点里插入自己的逻辑。
📌 打个比方:
beforeCreate
就像刚出生的婴儿,什么都还没有;created
就像长大拥有了思想,但还看不见世界;mounted
就像登上舞台,终于亮相;updated
就像换上新衣,焕然一新;unmounted
就是彻底谢幕,离开舞台。
第二章:详细解析每个生命周期阶段
创建阶段(组件诞生)
<template>
<div class="lifecycle-demo">
<h3>{{ title }}</h3>
<p>计数: {{ count }}</p>
<button @click="count++">增加</button>
<div ref="messageElement">我是DOM元素</div>
</div>
</template>
<script>
export default {
data() {
return {
title: '生命周期演示',
count: 0
}
},
// 1. beforeCreate - 组件刚初始化
beforeCreate() {
console.log('🚀 beforeCreate 阶段')
console.log('数据:', this.title) // undefined - 数据还未初始化
console.log('DOM:', this.$refs.messageElement) // undefined - DOM不存在
console.log('-------------------')
},
// 2. created - 数据已准备好
created() {
console.log('✅ created 阶段')
console.log('数据:', this.title) // '生命周期演示' - 数据已初始化
console.log('DOM:', this.$refs.messageElement) // undefined - DOM还未创建
console.log('可以在这里调用API获取数据')
console.log('-------------------')
// 常用场景:初始化数据、调用API
this.fetchData()
},
methods: {
fetchData() {
// 模拟API调用
console.log('正在从服务器获取数据...')
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
挂载阶段(显示到页面)
<script>
export default {
// ... 之前的代码
// 3. beforeMount - 模板编译完成,即将显示
beforeMount() {
console.log('📝 beforeMount 阶段')
console.log('DOM:', this.$refs.messageElement) // undefined - 还未插入页面
console.log('模板已编译,但还未挂载到DOM中')
console.log('-------------------')
},
// 4. mounted - 组件已显示在页面
mounted() {
console.log('🎉 mounted 阶段')
console.log('DOM:', this.$refs.messageElement) // 真实的DOM元素
console.log('组件已显示在页面中,可以操作DOM了')
console.log('-------------------')
// 常用场景:操作DOM、初始化第三方库、添加事件监听
this.initThirdPartyLibrary()
this.startTimer()
},
methods: {
initThirdPartyLibrary() {
// 比如初始化图表库、地图等
console.log('初始化ECharts图表...')
},
startTimer() {
// 启动定时器
this.timer = setInterval(() => {
console.log('定时器运行中...')
}, 1000)
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
更新阶段(数据变化时)
<script>
export default {
// ... 之前的代码
// 5. beforeUpdate - 数据变化,但DOM还未更新
beforeUpdate() {
console.log('🔄 beforeUpdate 阶段')
console.log('数据已变化,新count:', this.count)
console.log('但DOM还未更新,页面显示的还是旧值')
console.log('-------------------')
},
// 6. updated - DOM已更新完成
updated() {
console.log('✨ updated 阶段')
console.log('DOM已更新完成,页面显示新值')
console.log('可以在这里获取更新后的DOM状态')
console.log('-------------------')
// 注意:不要在这里修改数据,可能导致无限循环!
// this.count++ // ❌ 危险操作!
}
}
</script>
2.4 销毁阶段(组件消亡)
js 体验AI代码助手 代码解读复制代码<script>
export default {
// ... 之前的代码
// 7. beforeUnmount - 组件即将被销毁
beforeUnmount() {
console.log('🛑 beforeUnmount 阶段')
console.log('组件即将被销毁,但还可以访问数据和方法')
console.log('这是清理工作的最后机会')
console.log('-------------------')
// 常用场景:清除定时器、取消网络请求、移除事件监听
this.cleanup()
},
// 8. unmounted - 组件已销毁
unmounted() {
console.log('💀 unmounted 阶段')
console.log('组件已完全销毁,所有东西都被清理了')
console.log('-------------------')
},
methods: {
cleanup() {
// 清除定时器
if (this.timer) {
clearInterval(this.timer)
console.log('定时器已清除')
}
// 取消网络请求
console.log('取消未完成的网络请求...')
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
第三章:组合式API中的生命周期
在 Vue3 的组合式 API 中,我们不再写 beforeCreate、mounted 这样的选项式,而是 通过 在 setup() 中调用函数 来注册生命周期钩子。
⚠️ 注意: 所有生命周期 API 必须 在 setup() 阶段同步调用,不能放在异步回调里。
onBeforeMount
时机:组件完成响应式状态设置,但 DOM 还没生成。即将执行首次渲染。
<script setup>
import { onBeforeMount } from 'vue'
onBeforeMount(() => {
console.log('📝 组件即将挂载,还没有DOM')
})
</script>
2
3
4
5
6
7
📌 常见用途:在渲染前做一些最后准备,比如打印日志、检查数据。
onMounted
时机:组件挂载到 DOM 后执行。此时可以安全操作 DOM 或使用第三方库。
<script setup>
import { ref, onMounted } from 'vue'
const el = ref(null)
onMounted(() => {
console.log('🎉 组件挂载完成,可以访问DOM:', el.value)
})
</script>
<template>
<div ref="el">Hello</div>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
📌 常见用途:初始化图表、地图、事件监听。
onBeforeUpdate
时机:组件因为响应式数据变化而更新 DOM 前调用。此时数据是新的,但 DOM 还是旧的。
<script setup>
import { ref, onBeforeUpdate } from 'vue'
const count = ref(0)
onBeforeUpdate(() => {
console.log('🔄 即将更新,旧的DOM还在,新的count:', count.value)
})
</script>
2
3
4
5
6
7
8
9
📌 常见用途:在更新前读取旧 DOM 状态。
onUpdated
时机:组件 DOM 更新完成后调用。
<script setup>
import { ref, onUpdated } from 'vue'
const count = ref(0)
onUpdated(() => {
console.log('✨ DOM已更新,现在按钮内容是:', document.getElementById('btn').textContent)
})
</script>
<template>
<button id="btn" @click="count++">{{ count }}</button>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
⚠️ 注意:不要在 onUpdated
中直接改数据,否则可能造成无限循环。
📌 常见用途:依赖更新后的 DOM 做副作用,比如滚动定位。
onBeforeUnmount
时机:组件即将卸载,但功能还在。可以做一些清理准备。
<script setup>
import { onBeforeUnmount } from 'vue'
onBeforeUnmount(() => {
console.log('🛑 组件即将卸载')
})
</script>
2
3
4
5
6
7
onUnmounted
时机:组件彻底卸载完成,所有响应式作用都停掉。
<script setup>
import { onMounted, onUnmounted } from 'vue'
let timer
onMounted(() => {
timer = setInterval(() => console.log('⏰ 计时器运行中'), 1000)
})
onUnmounted(() => {
clearInterval(timer)
console.log('💀 已清理资源')
})
</script>
2
3
4
5
6
7
8
9
10
11
12
13
📌 常见用途:清理定时器、事件监听、网络连接。
onErrorCaptured
时机:捕获到子组件的错误时触发。
<script setup>
import { onErrorCaptured } from 'vue'
onErrorCaptured((err, instance, info) => {
console.error('❌ 捕获到错误:', err, '来源:', info)
return false // 阻止继续向上传递
})
</script>
2
3
4
5
6
7
8
📌 常见用途:集中处理错误,比如显示错误 UI。
onRenderTracked
/ onRenderTriggered
仅开发模式可用,用于调试响应式依赖。
<script setup>
import { onRenderTracked, onRenderTriggered } from 'vue'
onRenderTracked(e => {
console.log('👀 追踪到依赖:', e)
})
onRenderTriggered(e => {
console.log('⚡ 渲染触发原因:', e)
})
</script>
2
3
4
5
6
7
8
9
10
11
📌 常见用途:性能调优,分析组件的渲染依赖。
onActivated / onDeactivated
时机:当组件被 缓存时:
- onActivated → 组件被激活
- onDeactivated → 组件被移出 DOM 但缓存还在
<script setup>
import { onActivated, onDeactivated } from 'vue'
onActivated(() => {
console.log('🔵 组件激活')
})
onDeactivated(() => {
console.log('⚪ 组件失活')
})
</script>
2
3
4
5
6
7
8
9
10
11
📌 常见用途:缓存组件时恢复/暂停功能(比如视频播放)。
onServerPrefetch
仅在 SSR(服务端渲染)中可用:在组件渲染前预取数据。
<script setup>
import { ref, onServerPrefetch, onMounted } from 'vue'
const data = ref(null)
onServerPrefetch(async () => {
data.value = await fetchOnServer()
})
onMounted(async () => {
if (!data.value) {
data.value = await fetchOnClient()
}
})
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
📌 常见用途:SSR 预取数据,避免白屏。
小结
组合式 API 的生命周期钩子基本覆盖了选项式的所有功能,还增加了调试(onRenderTracked)、缓存组件(onActivated)和 SSR(onServerPrefetch)相关钩子。
👉 记忆方式:
- 挂载前后 → onBeforeMount / onMounted
- 更新前后 → onBeforeUpdate / onUpdated
- 卸载前后 → onBeforeUnmount / onUnmounted
- 调试 & 错误 → onErrorCaptured / onRenderTracked / onRenderTriggered
- 缓存组件 → onActivated / onDeactivated
- SSR → onServerPrefetch
第四章:实际应用场景
数据获取场景
<template>
<div class="user-profile">
<div v-if="loading">加载中...</div>
<div v-else-if="error">加载失败: {{ error }}</div>
<div v-else>
<h3>{{ user.name }}</h3>
<p>邮箱: {{ user.email }}</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
user: null,
loading: false,
error: null
}
},
async created() {
// 在created中获取数据,这样组件一创建就开始请求
await this.fetchUserData()
},
methods: {
async fetchUserData() {
this.loading = true
this.error = null
try {
// 模拟API调用
const response = await this.$http.get('/api/user/1')
this.user = response.data
} catch (err) {
this.error = err.message
} finally {
this.loading = false
}
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
第三方库集成
<template>
<div ref="chartContainer" style="width: 600px; height: 400px;"></div>
</template>
<script>
import * as echarts from 'echarts'
export default {
data() {
return {
chart: null
}
},
mounted() {
// 在mounted中初始化图表,因为这时DOM已经存在
this.initChart()
},
beforeUnmount() {
// 组件销毁前清理图表实例,避免内存泄漏
if (this.chart) {
this.chart.dispose()
}
},
methods: {
initChart() {
this.chart = echarts.init(this.$refs.chartContainer)
const option = {
title: { text: '销售图表' },
xAxis: { data: ['1月', '2月', '3月'] },
yAxis: {},
series: [{ type: 'bar', data: [100, 200, 150] }]
}
this.chart.setOption(option)
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
事件监听管理
<template>
<div class="resize-demo">
<p>窗口宽度: {{ windowWidth }}px</p>
</div>
</template>
<script>
export default {
data() {
return {
windowWidth: 0
}
},
mounted() {
// 添加事件监听
this.windowWidth = window.innerWidth
window.addEventListener('resize', this.handleResize)
},
beforeUnmount() {
// 移除事件监听,避免内存泄漏
window.removeEventListener('resize', this.handleResize)
},
methods: {
handleResize() {
this.windowWidth = window.innerWidth
console.log('窗口大小变化:', this.windowWidth)
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
第五章:常见问题和最佳实践
生命周期执行顺序
<!-- ParentComponent.vue -->
<template>
<ChildComponent />
</template>
<script>
export default {
beforeCreate() { console.log('父组件 beforeCreate') },
created() { console.log('父组件 created') },
beforeMount() { console.log('父组件 beforeMount') },
mounted() { console.log('父组件 mounted') }
}
</script>
<!-- ChildComponent.vue -->
<script>
export default {
beforeCreate() { console.log('子组件 beforeCreate') },
created() { console.log('子组件 created') },
beforeMount() { console.log('子组件 beforeMount') },
mounted() { console.log('子组件 mounted') }
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
执行顺序:
父组件 beforeCreate
父组件 created
父组件 beforeMount
子组件 beforeCreate
子组件 created
子组件 beforeMount
子组件 mounted
父组件 mounted
2
3
4
5
6
7
8
避免的陷阱
<script>
export default {
data() {
return {
items: []
}
},
// ❌ 错误:在beforeCreate中访问数据
beforeCreate() {
console.log(this.items) // undefined
},
// ❌ 错误:在created中操作DOM
created() {
document.querySelector('.my-element') // null 或 错误的元素
},
// ❌ 错误:在updated中修改数据(可能导致无限循环)
updated() {
if (this.items.length > 10) {
this.items.pop() // 危险!
}
},
// ✅ 正确:在created中初始化数据
created() {
this.fetchData()
},
// ✅ 正确:在mounted中操作DOM
mounted() {
this.$refs.myElement.focus()
},
// ✅ 正确:使用计算属性或侦听器替代updated中的数据修改
watch: {
items: {
handler(newItems) {
if (newItems.length > 10) {
this.items = newItems.slice(0, 10)
}
},
deep: true
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
第六章:生命周期记忆口诀
为了方便记忆,可以记住这个口诀:
"创挂更销"四大阶段
创(创建阶段)
beforeCreate - 初生婴儿,什么都没有
created - 有了思想,但看不见世界
挂(挂载阶段)
beforeMount - 准备好亮相,但还在后台
mounted - 登台表演,观众可见
更(更新阶段)
beforeUpdate - 准备换装,旧装还在身
updated - 新装亮相,焕然一新
销(销毁阶段)
beforeUnmount - 准备谢幕,还能说话
unmounted - 完全退场,清理舞台
2
3
4
5
6
7
8
9
10
11
12
13
14
15
总结
通过本文的学习,你应该已经掌握了:
- ✅ 生命周期概念 - 组件从创建到销毁的完整过程
- ✅ 8个生命周期钩子 - 每个阶段的作用和时机
- ✅ 组合式API用法 - 新的生命周期函数使用方式
- ✅ 实际应用场景 - 数据获取、第三方库集成等
- ✅ 最佳实践 - 避免常见陷阱,写出更好的代码
记住生命周期的重要性:合理使用生命周期钩子可以让你的组件更加健壮,避免内存泄漏,提升用户体验。