一、生命周期是什么?
简单理解:就像人的一生有"出生→成长→工作→退休"一样,Vue组件也有从创建到销毁的各个阶段,这些阶段就是"生命周期"。
二、Vue 3 生命周期全景图
text
创建组件 → setup() → onBeforeMount → 渲染DOM → onMounted → 组件活跃
↓
更新数据 → onBeforeUpdate → 重新渲染 → onUpdated → 可能再次更新
↓
组件销毁 → onBeforeUnmount → 清理工作 → onUnmounted → 组件消失
三、8个核心生命周期详解(附代码例子)
1. setup() - 组件的"出生前准备"
js
// 这是Vue 3的新特性,是组合式API的入口
import { ref } from 'vue'
export default {
setup() {
console.log('1. setup - 最先执行,准备数据和函数')
const count = ref(0) // 定义响应式数据
const add = () => { count.value++ }
return { count, add } // 返回给模板使用
}
}
作用:初始化响应式数据、计算属性、方法等
最佳实践:所有组件的逻辑都应该从这里开始组织
2. onBeforeMount() - 挂载前的最后检查
js
import { onBeforeMount, ref } from 'vue'
export default {
setup() {
const message = ref('Hello')
onBeforeMount(() => {
console.log('2. 挂载前 - DOM还没生成,不能操作DOM')
console.log('message:', message.value) // 可以访问数据
// document.getElementById('app') // ❌ 这里获取不到DOM元素
})
return { message }
}
}
作用:在组件挂载到DOM之前执行
最佳实践:可以在这里修改数据,但不能操作DOM
3. onMounted() - 组件"上岗工作"
js
import { onMounted, ref } from 'vue'
export default {
setup() {
const titleRef = ref(null) // 模板引用
onMounted(() => {
console.log('3. 已挂载 - 组件已插入DOM,可以操作DOM了')
// ✅ 可以获取DOM元素
console.log('标题元素:', titleRef.value)
// ✅ 可以调用第三方库
// this.chart = echarts.init(this.$refs.chart)
// ✅ 可以发送初始数据请求
// fetchData()
})
return { titleRef }
},
template: '<h1 ref="titleRef">我是标题</h1>'
}
作用:组件已挂载到DOM,可以操作DOM、调用第三方库、发送网络请求
最佳实践:
- 在此处进行DOM操作
- 发送初始化网络请求
- 启动定时器
- 绑定自定义事件
4. onBeforeUpdate() - 更新前的准备
js
import { onBeforeUpdate, ref } from 'vue'
export default {
setup() {
const count = ref(0)
onBeforeUpdate(() => {
console.log('4. 更新前 - 数据已变化,但页面还没重新渲染')
console.log('当前count值:', count.value)
})
return { count }
}
}
作用:响应式数据变化后,DOM重新渲染前
最佳实践:可以获取更新前的DOM状态
5. onUpdated() - 更新完成
js
import { onUpdated, ref } from 'vue'
export default {
setup() {
const count = ref(0)
onUpdated(() => {
console.log('5. 更新完成 - DOM已重新渲染')
// console.log('新的DOM内容:', document.getElementById('counter').textContent)
})
return { count }
},
template: '<div id="counter">{{ count }}</div>'
}
重要提醒:避免在onUpdated中修改数据,可能导致无限循环!
最佳实践:
- 执行依赖于DOM更新的操作
- 与父组件通信(通过$emit)
6. onBeforeUnmount() - 退休前的交接
js
import { onBeforeUnmount, ref } from 'vue'
export default {
setup() {
const timer = ref(null)
onBeforeUnmount(() => {
console.log('6. 卸载前 - 组件还在,但即将卸载')
console.log('清理前状态:', document.contains(this.$el))
})
return {}
}
}
作用:组件实例销毁前,此时组件功能完全正常
最佳实践:取消未完成的异步操作
7. onUnmounted() - 组件"退休"
js
import { onUnmounted, onMounted, ref } from 'vue'
export default {
setup() {
let timer = null
onMounted(() => {
console.log('组件已挂载')
timer = setInterval(() => {
console.log('计时器运行中...')
}, 1000)
})
onUnmounted(() => {
console.log('7. 已卸载 - 组件已销毁')
clearInterval(timer) // ✅ 必须清理!
timer = null
})
return {}
}
}
作用:组件销毁后执行清理工作
最佳实践(必须做!):
- 清除定时器
- 取消事件监听
- 清理全局状态
- 关闭WebSocket连接
销毁的实际场景
场景1:页面切换
js
// 路由配置
const routes = [
{ path: '/home', component: Home },
{ path: '/about', component: About }
]
// 从 /home 切换到 /about 时:
// 1. Home 组件被销毁
// 2. About 组件被创建
场景2:条件渲染
html
<template>
<div>
<button @click="show = !show">切换</button>
<!-- v-if 为 false 时,Timer 组件被销毁 -->
<Timer v-if="show" />
</div>
</template>
场景3:列表项删除
html
<template>
<div v-for="item in list" :key="item.id">
<ListItem :item="item" />
<button @click="remove(item.id)">删除</button>
</div>
</template>
<script setup>
// 点击删除时,对应的 ListItem 组件被销毁
</script>
8. onErrorCaptured() - 错误处理
js
import { onErrorCaptured } from 'vue'
export default {
setup() {
onErrorCaptured((error, instance, info) => {
console.log('捕获到错误:', error)
console.log('错误组件:', instance)
console.log('错误信息:', info)
// 可以上报错误到服务器
// sendErrorToServer(error)
return false // 阻止错误继续向上传播
})
return {}
}
}
作用:捕获子组件的错误
最佳实践:全局错误处理、错误上报
四、完整示例:一个计时器组件
html
<template>
<div>
<h1>计时器: {{ count }} 秒</h1>
<button @click="toggleTimer">{{ isRunning ? '暂停' : '开始' }}</button>
<button @click="reset">重置</button>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, onBeforeUnmount } from 'vue'
const count = ref(0)
const isRunning = ref(false)
let timer = null
// 1. 组件挂载时
onMounted(() => {
console.log('组件已挂载,准备开始计时')
startTimer()
})
// 2. 开始/暂停计时
const toggleTimer = () => {
isRunning.value = !isRunning.value
if (isRunning.value) {
startTimer()
} else {
stopTimer()
}
}
// 3. 启动计时器
const startTimer = () => {
if (!timer) {
timer = setInterval(() => {
count.value++
console.log(`计时: ${count.value}秒`)
}, 1000)
}
}
// 4. 停止计时器
const stopTimer = () => {
if (timer) {
clearInterval(timer)
timer = null
}
}
// 5. 重置
const reset = () => {
count.value = 0
}
// 6. 组件销毁前
onBeforeUnmount(() => {
console.log('组件即将销毁,准备清理...')
})
// 7. 组件销毁时(最重要!)
onUnmounted(() => {
console.log('组件已销毁')
stopTimer() // 必须清理定时器!
})
</script>
五、生命周期最佳实践总结
| 生命周期 | 应该做什么 | 不应该做什么 |
|---|---|---|
| setup | 定义数据、方法、计算属性 | 操作DOM、发送请求 |
| onMounted | 操作DOM、发送请求、启动定时器 | 修改大量数据导致频繁更新 |
| onUpdated | 执行DOM更新后的操作 | 修改数据(可能导致无限循环) |
| onUnmounted | 清理定时器、取消事件、关闭连接 | 调用组件方法(组件已不存在) |
六、常见问题解答
Q1:created和beforeCreate去哪了?
A:在Vue 3的组合式API中,setup()替代了这两个钩子
Q2:onMounted和onUpdated哪个先执行?
A:首次加载时只执行onMounted,数据更新时才执行onUpdated
Q3:多个相同生命周期会按什么顺序执行?
A:按照代码书写的顺序执行
Q4:父组件和子组件的生命周期顺序?
A:
markdown
父beforeCreate → 父created → 父beforeMount
↓
子beforeCreate → 子created → 子beforeMount → 子mounted
↓
父mounted
七、一句话记住每个生命周期
- setup - 出生前,准备东西
- onBeforeMount - 要上岗了,最后检查
- onMounted - 已上岗,开始工作
- onBeforeUpdate - 要改方案,先想想
- onUpdated - 方案已改,完成
- onBeforeUnmount - 要退休了,交接工作
- onUnmounted - 已退休,清理办公桌
- onErrorCaptured - 下属犯错,我来处理