你是不是也曾经在 Vue 项目中遇到过这样的问题:在 mounted 钩子里获取 $refs,结果发现是 undefined?或者搞不清楚到底该在 created 还是 mounted 里发起数据请求?
别担心,今天我就带你彻底搞懂 Vue 的生命周期,让你再也不踩这些坑!
Vue 生命周期流程
想象一下,一个 Vue 组件就像一个人的人生历程:
- 出生前(beforeCreate):还没任何属性,连 data 都没有
- 婴儿期(created):有了 data 和 methods,但还没挂载到页面上
- 上学前(beforeMount):准备挂载到 DOM,但还没真正显示
- 成年工作(mounted):已经在页面上显示了,可以操作 DOM 了
- 更新阶段(beforeUpdate/updated):数据变化时的反应
- 退休(beforeUnmount/unmounted):组件被销毁前的清理工作
是不是一下子好理解多了?
Vue 2 和 Vue 3 的生命周期对比
很多人在用 Vue 3 时发现,以前熟悉的 beforeDestroy 和 destroyed 不见了,变成了 beforeUnmount 和 unmounted。其实功能是一样的,只是名字更准确了。
Vue 2 用的是选项式 API,生命周期钩子直接写在 options 里。Vue 3 兼容这种方式,但更推荐用组合式 API,在 setup 函数里使用生命周期钩子。
最常用的几个钩子,到底该怎么用?
created:最早的初始化时机
created 钩子在组件实例创建完成后立即调用,这时候已经可以访问到 data、methods 等,但还没挂载到 DOM 上。
javascript
export default {
data() {
return {
userList: []
}
},
async created() {
// 在这里发起数据请求很合适
// 因为组件已经初始化完成,但还没渲染到页面上
try {
const response = await fetch('/api/users')
this.userList = await response.json()
} catch (error) {
console.error('获取用户列表失败:', error)
}
}
}
mounted:操作 DOM 的最佳时机
mounted 在组件挂载到 DOM 后调用,这时候可以安全地操作 DOM 元素了。
但这里有个常见陷阱:有时候在 mounted 里获取 $refs 却是 undefined!
javascript
export default {
mounted() {
// 这里可以操作 DOM 了
console.log('组件已挂载')
// 但要注意:如果组件里有 v-if 条件渲染
// 可能某些 $refs 还是拿不到
if (this.$refs.myButton) {
this.$refs.myButton.addEventListener('click', this.handleClick)
}
},
methods: {
handleClick() {
console.log('按钮被点击了')
}
}
}
为什么有时候 $refs 是 undefined?通常是因为:
- 元素被 v-if 控制,初始条件为 false
- 在元素渲染完成前就尝试访问 $refs
- 组件还未完全挂载完成
解决办法是用 $nextTick 确保 DOM 更新完成后再操作:
javascript
export default {
mounted() {
this.$nextTick(() => {
// 现在肯定能拿到 $refs 了
if (this.$refs.myInput) {
this.$refs.myInput.focus()
}
})
}
}
updated:数据变化后的操作
updated 在数据更改导致的 DOM 更新完成后调用,但要小心不要在这里修改状态,否则可能导致无限循环!
javascript
export default {
data() {
return {
count: 0
}
},
updated() {
// 这里可以获取更新后的 DOM 状态
console.log('数据更新了,当前count:', this.count)
// 危险操作:可能引起无限更新循环
// this.count++
// 安全操作:只是读取不修改
const element = document.getElementById('counter')
if (element) {
console.log('元素内容:', element.textContent)
}
}
}
unmounted:清理工作的最后机会
在 Vue 3 中,用 unmounted 替代了 Vue 2 的 destroyed,用于执行清理操作,比如移除事件监听器、取消定时器等。
javascript
import { onUnmounted } from 'vue'
export default {
setup() {
let intervalId = null
const startTimer = () => {
intervalId = setInterval(() => {
console.log('定时器执行中...')
}, 1000)
}
onUnmounted(() => {
// 组件销毁前清除定时器,避免内存泄漏
if (intervalId) {
clearInterval(intervalId)
console.log('定时器已清理')
}
})
return {
startTimer
}
}
}
实际开发中的常见场景
场景一:何时发起数据请求?
通常建议在 created 中发起数据请求,因为这时候组件已经初始化完成,可以更早地开始获取数据,减少用户等待时间。
但有些特殊情况需要在 mounted 中请求,比如需要获取元素尺寸等 DOM 信息的情况。
场景二:监听窗口大小变化
javascript
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
}
}
}
场景三:集成第三方库
很多第三方库(如图表库、地图库)需要在 DOM 可用后才能初始化,这时候 mounted 是最合适的时机。
javascript
export default {
mounted() {
// 初始化图表
this.initChart()
},
methods: {
initChart() {
// 假设使用 echarts
const chart = echarts.init(this.$refs.chartContainer)
chart.setOption({
// 图表配置
series: [{
type: 'bar',
data: [10, 20, 30, 40, 50]
}]
})
}
}
}
避免这些生命周期陷阱
-
不要在 updated 中修改状态 - 这可能导致无限更新循环
-
记得清理副作用 - 事件监听器、定时器、订阅等都要在 unmounted 中清理
-
理解异步更新队列 - Vue 会批量执行更新,使用 $nextTick 确保 DOM 更新完成
-
注意 v-if 对生命周期的影响 - 被 v-if 控制的组件会完全销毁和重新创建,而不是隐藏和显示
总结一下
Vue 生命周期就像是组件的"人生阶段",每个阶段都有其特定的用途和注意事项:
- created:初始化数据,发起请求
- mounted:操作 DOM,集成第三方库
- updated:响应数据变化后的 DOM 更新
- unmounted:清理工作,避免内存泄漏
最重要的是理解每个钩子的执行时机和适用场景,这样在开发中就能做出正确的选择。
你在使用 Vue 生命周期时还遇到过哪些坑?或者有什么特别的使用技巧?欢迎在评论区分享你的经验!
对了,如果你觉得这篇文章对你有帮助,记得点赞收藏,下次遇到生命周期的问题就不会迷路啦~