Vue3 生命周期深度解析
前言:很多人学 Vue3 生命周期只停留在“onMounted 操作 DOM、onUnmounted 清除定时器”,但真正决定项目稳定性、性能、可维护性的,是生命周期执行机制、副作用清理模型、setup 内部执行上下文、与渲染流程的关系。本文从原理到实战,把 Vue3 生命周期讲透,适合中高级前端直接用于项目架构与避坑。
一、先破误区:Vue3 生命周期不是“换个名字”那么简单
很多迁移文章只给对照表,却不说本质变化:
- Vue2 生命周期 = 选项式 + 实例阶段强绑定
- Vue3 生命周期 = 组合式 + 副作用调度模型
- setup 不是一个生命周期,而是整个组合式 API 的执行入口
- onMounted 本质是“调度任务”,不是简单回调
理解这 4 点,才算真正入门 Vue3 生命周期。
二、Vue3 完整生命周期执行时序(最硬核的一张执行链)
组件从创建到销毁完整流程
setup 执行
↓
onBeforeMount
↓
【模板编译 / 渲染函数执行 / 生成 VNode Tree】
↓
【DOM 挂载、patch 完成】
↓
onMounted
↓
────────────────────
数据更新触发:
onBeforeUpdate
↓
【DOM diff + 重新渲染】
↓
onUpdated
────────────────────
↓
组件卸载触发:
onBeforeUnmount
↓
【DOM 移除、子树卸载、副作用清理】
↓
onUnmounted
关键结论(深度考点)
- setup 执行最早,早于所有生命周期
- onMounted 是在 DOM 真实插入页面后才执行
- onBeforeUpdate 执行时,DOM 还是旧的
- onUpdated 执行时,DOM 已经是新的,但可能还未完成浏览器重绘
- onUnmounted 触发时,Vue3 会自动清理当前组件实例关联的所有响应式 effect(包括 watch、computed 的依赖追踪 effect),但手动创建的定时器、全局事件监听、第三方库实例等「非 Vue 内置副作用」,仍需手动清理
三、Vue2 → Vue3 生命周期深度对比(不只对应,而是原理差异)
| 阶段 | Vue2 | Vue3 组合式 | 核心差异 |
|---|---|---|---|
| 实例初始化 | beforeCreate | setup 开头 | Vue3 无实例 this,全部基于闭包与副作用 |
| 数据就绪 | created | setup 内部 | setup 同步代码 = 天然替代 created |
| 挂载前 | beforeMount | onBeforeMount | Vue3 此时 VNode 已生成,但未插入 DOM |
| 挂载完成 | mounted | onMounted | Vue3 是微任务调度,执行时机更稳定 |
| 更新前 | beforeUpdate | onBeforeUpdate | 此时视图未更新 |
| 更新完成 | updated | onUpdated | 可能触发多次,慎用 |
| 卸载前 | beforeDestroy | onBeforeUnmount | 改名+语义更清晰 |
| 卸载完成 | destroyed | onUnmounted | 自动清理 effect,Vue2 需手动 |
| 缓存组件 | activated | onActivated | 只在 keep-alive 内生效 |
四、核心原理:为什么 Vue3 要把生命周期设计成“函数调用”?
1. 脱离 this 耦合
Vue2 生命周期强依赖 this,导致逻辑无法抽离成独立函数。Vue3 通过副作用收集机制,让生命周期可以写在任何函数里(包括 hooks)。
2. 支持“多实例隔离”
同一个 hook 可以在组件内多次调用,不会互相污染。
3. 与响应式系统深度绑定
所有生命周期本质都是:在渲染流程的特定时机,插入一段 effect 任务。
这也是为什么:
- onMounted 能保证 DOM 存在
- onUnmounted 能自动清理 Vue 内置副作用
- watch 本质也是一种特殊生命周期
五、生产级标准写法(无冗余、可直接复制进项目)
5.1 最标准的 script setup 生命周期模板
<template>
<div ref="chartRef" class="demo-box"></div>
</template>
<script setup>
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onActivated,
onDeactivated
} from 'vue'
import { useRequest } from '@/hooks/useRequest'
import { initChart, destroyChart } from '@/utils/echarts'
// 响应式
const chartRef = ref(null)
let timer = null
let resizeObserver = null
// ==========================================
// 对应 beforeCreate + created
// ==========================================
console.log('setup 执行:组件初始化阶段')
const { data, loading } = useRequest('/api/list')
// ==========================================
// 挂载前:准备配置
// ==========================================
onBeforeMount(() => {
console.log('onBeforeMount:VNode 已生成,DOM 未挂载')
})
// ==========================================
// 挂载后:DOM 操作、第三方库、事件绑定
// ==========================================
onMounted(() => {
console.log('onMounted:DOM 已挂载')
// 初始化图表
if (chartRef.value) {
initChart(chartRef.value)
}
// 定时器
timer = setInterval(() => { /* 业务逻辑 */ }, 1000)
// 监听窗口变化
resizeObserver = new ResizeObserver(entries => {
// 图表自适应
})
resizeObserver.observe(chartRef.value)
})
// ==========================================
// 更新前后(一般业务极少使用)
// ==========================================
onBeforeUpdate(() => {
// 旧 DOM 状态
})
onUpdated(() => {
// 新 DOM 已渲染
})
// ==========================================
// keep-alive 缓存组件生命周期
// ==========================================
onActivated(() => {
console.log('组件从缓存激活')
})
onDeactivated(() => {
console.log('组件进入缓存')
})
// ==========================================
// 卸载前:必须清理!防止内存泄漏
// ==========================================
onBeforeUnmount(() => {
console.log('onBeforeUnmount:组件即将卸载')
// 清理定时器(手动创建的非Vue内置副作用)
if (timer) clearInterval(timer)
// 清理 ResizeObserver / 事件监听(手动创建的非Vue内置副作用)
if (resizeObserver) {
resizeObserver.disconnect()
}
// 销毁第三方实例(手动创建的非Vue内置副作用)
if (chartRef.value) {
destroyChart(chartRef.value)
}
})
onUnmounted(() => {
console.log('onUnmounted:组件已完全卸载')
})
</script>
六、真正高级的部分:生命周期底层机制(文章深度核心)
6.1 onMounted 到底是怎么保证 DOM 存在的?
Vue3 在 patch 流程完成 DOM 插入后,会执行:
queuePostRenderEffect(() => {
// 所有 onMounted 回调在这里执行
})
它是一个微任务队列,保证:
- 所有子组件挂载完成
- 所有 DOM 插入结束
- 同步代码执行完毕
所以你在 onMounted 里一定能拿到 ref DOM。
6.2 onUnmounted 为什么能自动清理?
因为 Vue3 对每个组件维护了:组件实例的副作用链表(effects),卸载时会遍历执行清理函数,仅针对 Vue 内置副作用:
- watch
- computed
- 生命周期回调
- 自定义 effect
这是 Vue2 不具备的自动内存管理机制,但手动创建的副作用仍需手动清理。
6.3 为什么不要在 setup 同步代码里操作 DOM?
setup 执行时:
- 没有 DOM
- 没有渲染
- ref 都是 null
- 模板还未生成 VNode
操作 DOM 必然报错。这是初级与中级开发者的核心区别。
七、企业级高频坑(90% 项目都踩过)
坑 1:onMounted 里获取 ref 依然为 null
原因
- v-if 控制的元素
- 父组件 suspense
- 动态组件 + keep-alive
生产解决方案
watch(
() => chartRef.value,
(val) => {
if (val) initChart(val)
},
{ immediate: true }
)
坑 2:onUpdated 无限循环更新
原因:在 onUpdated 中修改了触发更新的响应式数据。
规范:onUpdated 只做读取,不做写入。
坑 3:定时器/事件监听泄漏
根本原因:没有理解 Vue3 卸载机制仅自动清理内置副作用,不会自动清理全局对象、手动创建的副作用。
工程化最佳实践:封装 useTimer、useEventListener、useResize 等自动清理 hooks。
坑 4:keep-alive 组件重复请求接口
原因:mounted 只执行一次,后续进入不会触发。
解决方案:接口放在 onActivated。
八、架构级最佳实践(真正拉开差距的内容)
1. 生命周期职责严格分层
- setup:数据初始化、请求封装
- onBeforeMount:配置准备
- onMounted:DOM、第三方库、监听
- onBeforeUnmount:统一清理手动副作用
- onUpdated:禁止写业务逻辑
2. 所有副作用必须封装成 Hook
// useChart.js
export function useChart(elRef) {
let chart = null
onMounted(() => { chart = init(elRef.value) })
onBeforeUnmount(() => { chart?.dispose() })
return { chart }
}
3. 禁止在生命周期中写复杂业务逻辑
复杂逻辑抽离 → composables,生命周期只负责“时机调度”。
4. 统一错误边界与异常捕获
onMounted(async () => {
try { /* ... */ }
catch (err) {
console.error(err)
}
})
九、总结
Vue3 生命周期不是简单的 API 改名,而是从“选项式阶段模型”升级为“副作用调度模型”。
真正掌握生命周期,要理解三件事:
- 执行时机与渲染流程的关系
- 副作用自动清理机制(区分内置与手动副作用)
- setup 与 hooks 的架构意义
只有从原理层面理解,才能写出无内存泄漏、高性能、可维护、可迁移的企业级 Vue3 项目。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)