Vue 生命周期深度解析:从 Vue 2 到 Vue 3 的设计演进
·
引言:生命周期的本质思考
前面讲了一些ref函数,接下来我们要讲不管v2还是v3还是其他的框架都是重要的一个概念。在深入探讨 Vue 生命周期之前,让我们先思考一个根本问题:为什么现代前端框架都需要生命周期概念?
想象一下建造一栋精装房的完整流程:
-
蓝图规划(设计图纸、准备材料)
-
主体施工(搭建结构、硬装完成)
-
软装入住(家具布置、开始使用)
-
日常维护(维修更新、功能升级)
-
拆迁重建(清理现场、资源回收)
Vue 组件的生命周期正是这样的"建筑时序图",它为开发者提供了在组件不同阶段执行代码的精确时间窗口。
一、Vue 2 生命周期:经典的八阶段模型
完整的生命周期图谱
创建阶段(Creation Phase):
beforeCreate → created
挂载阶段(Mounting Phase):
beforeMount → mounted
更新阶段(Update Phase):
beforeUpdate → updated
销毁阶段(Destruction Phase):
beforeDestroy → destroyed
各阶段深度解析
1. 创建阶段 - 组件的"孕育期"
export default {
beforeCreate() {
// 组件实例刚被创建,但数据观测、事件配置都未初始化
console.log('beforeCreate - 胚胎形成,但尚无生命特征');
console.log(this.message); // undefined
console.log(this.$el); // undefined
},
created() {
// 组件实例创建完成,数据观测、计算属性、方法已配置
// 但 DOM 树还未生成,模板编译未开始
console.log('created - 生命体征已具备,但尚未有形体');
console.log(this.message); // 可访问数据
this.fetchData(); // 可调用方法
console.log(this.$el); // undefined,DOM 仍未就绪
}
}
实际应用场景:
-
created:API 数据请求、事件监听初始化、非 DOM 依赖的初始化逻辑
2. 挂载阶段 - 组件的"诞生礼"
export default {
beforeMount() {
// 模板编译完成,虚拟 DOM 已创建,但尚未挂载到真实 DOM
console.log('beforeMount - 形体已设计完成,等待降临现实世界');
console.log(this.$el); // 仍然是编译前的模板
},
mounted() {
// 组件已挂载到真实 DOM,可以访问 DOM 元素
console.log('mounted - 已降临现实世界,可与用户交互');
console.log(this.$el); // 真实的 DOM 元素
this.initChart(); // 初始化需要 DOM 的第三方库
}
}
3. 更新阶段 - 组件的"成长轨迹"
export default {
beforeUpdate() {
// 数据变化,虚拟 DOM 重新渲染和打补丁之前
console.log('beforeUpdate - 即将成长变化,当前状态:', this.message);
},
updated() {
// 虚拟 DOM 重新渲染和打补丁完成
console.log('updated - 成长完成,新状态:', this.message);
// 可执行依赖新 DOM 的操作
}
}
4. 销毁阶段 - 组件的"生命终结"
export default {
beforeDestroy() {
// 实例销毁之前,此时实例仍然完全可用
console.log('beforeDestroy - 生命即将终结,整理遗物');
this.clearTimers(); // 清除定时器
this.removeListeners(); // 移除事件监听
},
destroyed() {
// 实例销毁后,所有绑定解除,子实例也被销毁
console.log('destroyed - 生命已终结,一切归于虚无');
}
}
二、父组件 vs 子组件:生命周期执行顺序
完整的执行顺序流程
父组件 beforeCreate
父组件 created
父组件 beforeMount
-> 开始处理子组件
子组件 beforeCreate
子组件 created
子组件 beforeMount
子组件 mounted
父组件 mounted
三、Vue 3 生命周期:组合式 API 的现代化重构
生命周期对照表
| Vue 2 | Vue 3 | 变化说明 |
|---|---|---|
beforeCreate |
移除 | 被 setup() 替代 |
created |
移除 | 被 setup() 替代 |
beforeMount |
onBeforeMount |
功能相同,命名更语义化 |
mounted |
onMounted |
功能相同,组合式 API 风格 |
beforeUpdate |
onBeforeUpdate |
功能相同 |
updated |
onUpdated |
功能相同 |
beforeDestroy |
onBeforeUnmount |
命名更准确(卸载而非销毁) |
destroyed |
onUnmounted |
命名更准确 |
Vue 3 组合式 API 实现
<template>
<div>{{ message }}</div>
</template>
<script setup>
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmount
} from 'vue'
const message = ref('Hello Vue 3!')
// 挂载阶段
onBeforeMount(() => {
console.log('onBeforeMount - 即将挂载到 DOM')
})
onMounted(() => {
console.log('onMounted - 已挂载完成,可访问 DOM')
// DOM 操作、第三方库初始化
})
// 更新阶段
onBeforeUpdate(() => {
console.log('onBeforeUpdate - 数据变化,更新前')
})
onUpdated(() => {
console.log('onUpdated - 更新完成')
})
// 卸载阶段
onBeforeUnmount(() => {
console.log('onBeforeUnmount - 组件即将卸载')
// 清理工作
})
onUnmounted(() => {
console.log('onUnmounted - 组件已卸载')
})
</script>
四、深度探讨:为什么 Vue 3 移除了创建阶段钩子?
设计哲学转变
Vue 2 的问题:
// Vue 2 中需要在不同生命周期分散逻辑
export default {
data() {
return { user: null, posts: [] }
},
created() {
this.fetchUser() // 用户数据请求
},
mounted() {
this.fetchPosts() // 文章数据请求
this.initPlugin() // 插件初始化
},
destroyed() {
this.cleanup() // 清理工作
}
}
Vue 3 的解决方案:
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
// 相关逻辑集中管理
const user = ref(null)
const posts = ref([])
// 用户相关逻辑
const fetchUser = async () => {
user.value = await api.getUser()
}
// 文章相关逻辑
const fetchPosts = async () => {
posts.value = await api.getPosts()
}
// 插件相关逻辑
const initPlugin = () => { /* ... */ }
const cleanup = () => { /* ... */ }
// 生命周期中组织逻辑
onMounted(async () => {
await fetchUser() // 原本在 created 的逻辑
await fetchPosts() // 原本在 mounted 的逻辑
initPlugin() // 原本在 mounted 的逻辑
})
onUnmounted(() => {
cleanup() // 清理逻辑
})
</script>
移除 beforeCreate 和 created 的真正原因
-
逻辑重复性:
setup()函数本身就是在组件创建阶段执行 -
代码组织性:组合式 API 让相关逻辑自然聚集,无需人为分割
-
简化模型:减少生命周期概念,降低学习成本
<script setup>
// 在 setup 中直接编写原本在 created 中的逻辑
const data = ref('这是数据初始化')
const method = () => { /* 方法定义 */ }
// 相当于 Vue2 的 created 阶段
console.log('这部分代码在组件创建时立即执行')
// 需要 DOM 的操作放到 onMounted 中
onMounted(() => {
console.log('需要 DOM 的操作在这里执行')
})
</script>
五、常用生命周期钩子实战指南
1. onMounted - 最常用的钩子
<template>
<div ref="chartContainer"></div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import * as echarts from 'echarts'
const chartContainer = ref(null)
let chartInstance = null
onMounted(() => {
// 初始化图表
chartInstance = echarts.init(chartContainer.value)
// 添加窗口 resize 监听
window.addEventListener('resize', handleResize)
// 获取初始数据
fetchData()
})
const handleResize = () => {
chartInstance?.resize()
}
</script>
2. onUpdated - 谨慎使用的钩子
<script setup>
import { ref, onUpdated, nextTick } from 'vue'
const list = ref([])
onUpdated(() => {
// 注意:这里要避免无限循环更新!
if (list.value.length > 10) {
// 执行某些依赖最新 DOM 状态的操作
}
})
// 更好的做法:使用 watch 或 nextTick
const addItem = async () => {
list.value.push(new Item())
await nextTick()
// DOM 更新后执行的操作
}
</script>
3. onBeforeUnmount - 资源清理专家
<script setup>
import { onBeforeUnmount } from 'vue'
let timer = null
// 设置定时器
timer = setInterval(() => {
console.log('定时执行')
}, 1000)
onBeforeUnmount(() => {
// 清理工作
clearInterval(timer)
timer = null
// 移除事件监听
window.removeEventListener('resize', handleResize)
// 取消未完成的请求
// abortController.abort()
})
</script>
六、版本对比与迁移策略
Vue 2 → Vue 3 生命周期迁移指南
// Vue 2
export default {
created() {
this.initData()
},
mounted() {
this.initDOM()
},
beforeDestroy() {
this.cleanup()
}
}
// Vue 3 等效写法
<script setup>
import { onMounted, onBeforeUnmount } from 'vue'
// created 逻辑直接写在 setup 中
initData()
onMounted(() => {
initDOM()
})
onBeforeUnmount(() => {
cleanup()
})
</script>
总结:生命周期的演进哲学
Vue 3 的生命周期设计体现了前端框架发展的几个重要趋势:
-
关注点分离 → 逻辑关注点聚合
-
时间轴驱动 → 逻辑功能驱动
-
配置化编程 → 组合式编程
-
概念复杂性 → 模型简化
给开发者的建议
-
Vue 3 项目:优先使用组合式 API,享受更自然的代码组织方式
-
迁移项目:理解设计哲学变化,不仅仅是 API 的机械替换
-
学习路径:先理解生命周期的本质,再掌握具体 API
-
最佳实践:在
setup中组织逻辑,在生命周期中安排执行时机
生命周期的演进不仅是 API 的变化,更是前端开发思维模式的升级。理解这背后的设计,比记忆具体的 API 更重要!
更多推荐
所有评论(0)