Vue 2 / Vue 3 生命周期:从创建到卸载(含执行顺序)
生命周期(Lifecycle)说白了就是:组件从“出生”到“死亡”这一路上,会在固定的时间点给你一些“插口”(钩子)让你写代码。
掌握生命周期最大的价值在于两点:
- 知道代码该放哪:什么时候能拿到数据?什么时候才能安全操作 DOM?什么时候必须做清理?
- 排查问题更快:比如“为什么 mounted 里拿不到最新 DOM”“为什么 keep-alive 切换没触发销毁”等。
下面我们把 Vue 2 和 Vue 3 的生命周期按“阶段 + 执行顺序”讲清楚,并补上父子组件的先后顺序。
挂载(mount)→ 更新(update)→ 卸载(unmount)。生命周期钩子就是围绕这三段展开的。
1. Vue 2 生命周期(Options API)
Vue 2 的常用生命周期钩子如下:
- 创建阶段:
beforeCreate→created - 挂载阶段:
beforeMount→mounted - 更新阶段:
beforeUpdate→updated - 销毁阶段:
beforeDestroy→destroyed - keep-alive 专属:
activated/deactivated - 异常捕获:
errorCaptured(捕获后代组件错误)
1.1 首次渲染(挂载)执行顺序
顺序:
beforeCreate:此时data/props还没初始化,this.xxx基本拿不到你想要的东西created:data/props/computed/methods已准备好,但 DOM 还没挂载beforeMount:即将把虚拟 DOM 渲染成真实 DOMmounted:真实 DOM 已挂载,适合做 DOM 读写、三方库初始化
created 时 DOM 还不存在。需要 DOM 的逻辑,请放到 mounted,或配合 this.$nextTick()。
1.2 数据更新(重新渲染)执行顺序
当响应式数据变化触发重新渲染时:
beforeUpdate:DOM 还没更新,你在这里读到的往往还是旧 DOMupdated:DOM 已更新完成,可以在这里读取更新后的 DOM(但别在这里再无脑改状态,容易循环更新)
1.3 销毁(卸载)执行顺序
当组件即将从页面移除时:
beforeDestroy:适合做清理(取消订阅、清除定时器、解绑事件等)destroyed:组件实例与响应式连接已断开,子组件也都被销毁
1.4 keep-alive:activated / deactivated 的顺序
被 <keep-alive> 缓存的组件,切换离开/回来时不会走“销毁”,而是:
- 首次进入:完整挂载流程 +
activated - 切换离开:
deactivated - 切换回来:
activated - 真正移除缓存(例如切换到不再命中、或缓存被淘汰):才会走
beforeDestroy/destroyed
需要“每次页面切回来刷新数据”的场景,优先考虑 activated,而不是 mounted。
2. Vue 3 生命周期(Options API + Composition API)
Vue 3 既支持 Options API(写法类似 Vue 2),也支持 Composition API(setup/<script setup>)。
2.1 命名变化:destroy → unmount
Vue 3 把“销毁(destroy)”这组名字改成了“卸载(unmount)”:
beforeDestroy→beforeUnmountdestroyed→unmounted
2.2 setup 的位置(非常重要)
在 Composition API 中:
setup()在组件创建之初执行,你可以把它理解为“更早的 created”setup()里拿不到this(因为还没有组件实例的 this 语义),逻辑都用局部变量、ref/reactive来组织onMounted/onUpdated/onUnmounted...这类钩子,都需要在setup()里注册
Vue 3 Composition API 下,没有“onCreated / onBeforeCreate”,因为 setup() 就是那一段。
2.3 Vue 2 / Vue 3 钩子对照表(背这一张基本够用)
| Vue 2(Options) | Vue 3(Options) | Vue 3(Composition) |
|---|---|---|
beforeCreate | beforeCreate(兼容) | 用 setup() 里的同步代码替代 |
created | created(兼容) | 用 setup() 里的同步代码替代 |
beforeMount | beforeMount | onBeforeMount |
mounted | mounted | onMounted |
beforeUpdate | beforeUpdate | onBeforeUpdate |
updated | updated | onUpdated |
beforeDestroy | beforeUnmount | onBeforeUnmount |
destroyed | unmounted | onUnmounted |
activated | activated | onActivated |
deactivated | deactivated | onDeactivated |
errorCaptured | errorCaptured | onErrorCaptured |
onMounted/onUpdated 只会在浏览器端执行;SSR 渲染阶段只会跑 setup() 以及与渲染相关的逻辑。
2.4 Vue 3(Composition API)执行顺序速记
把 Vue 3 的 Composition API 生命周期按阶段记最省事:
- 首次挂载:
setup()→onBeforeMount→onMounted - 更新:
onBeforeUpdate→onUpdated - 卸载:
onBeforeUnmount→onUnmounted - keep-alive:
onActivated/onDeactivated
如果你同时写了 setup() 和 beforeCreate/created(不推荐但可能存在),setup() 会更早执行;之后才进入 Options API 的生命周期钩子。
3. 执行顺序:父子组件谁先谁后(面试高频)
生命周期的“单组件顺序”好背,但真正容易栽坑的是 父子组件的先后顺序。
下面给出最常见的执行规律(Vue 2 / Vue 3 都基本一致):
3.1 挂载:先父后子,mounted 反过来
典型顺序:
- 父:
beforeCreate→created→beforeMount - 子:
beforeCreate→created→beforeMount→mounted - 父:
mounted
3.2 更新:先父后子,updated 反过来
当父组件的数据变化导致父子都需要重新渲染时:
- 父:
beforeUpdate - 子:
beforeUpdate - 子:
updated - 父:
updated
如果“父更新不影响子”(比如子组件不依赖变化的 props、或子组件被 v-once 固定),子组件可能不会触发更新钩子。
3.3 卸载:先父后子,unmounted 反过来
卸载时的规律也类似:
- 父:
beforeUnmount(Vue 2 是beforeDestroy) - 子:
beforeUnmount/beforeDestroy - 子:
unmounted/destroyed - 父:
unmounted/destroyed
4. 用法指南:逻辑到底放哪个钩子?
把“放哪”这件事说清楚,你生命周期就算真正学会了。
4.1 请求数据:created / setup 更常用
- Vue 2:通常放
created(拿得到 data/props,且越早越好) - Vue 3:通常放
setup()(或在setup里触发请求)
如果请求依赖 DOM 尺寸(比如要读容器宽高再请求),那必须放到 mounted/onMounted 后。
4.2 操作 DOM:mounted / onMounted + nextTick
需要拿元素、读尺寸、初始化图表库(ECharts)等:
- Vue 2:
mounted,必要时this.$nextTick(() => {}) - Vue 3:
onMounted,必要时nextTick(() => {})
4.3 清理副作用:beforeUnmount / onBeforeUnmount
下面这些如果不清理,最容易内存泄漏:
setInterval/setTimeoutwindow.addEventListener- WebSocket / EventSource
- 自己写的订阅(pub/sub)
Vue 2 推荐放 beforeDestroy,Vue 3 推荐放 onBeforeUnmount 或 onUnmounted。
5. 例子:打印完整执行顺序(Vue 2 / Vue 3)
5.1 Vue 2(父子组件日志)
父组件与子组件各写一套钩子,刷新页面你就能看到顺序:
// Parent.vue(示例)
export default {
name: 'Parent',
beforeCreate() { console.log('P beforeCreate') },
created() { console.log('P created') },
beforeMount() { console.log('P beforeMount') },
mounted() { console.log('P mounted') },
beforeUpdate() { console.log('P beforeUpdate') },
updated() { console.log('P updated') },
beforeDestroy() { console.log('P beforeDestroy') },
destroyed() { console.log('P destroyed') },
}
你会得到类似这样的输出(仅展示挂载阶段):
P beforeCreate
P created
P beforeMount
C beforeCreate
C created
C beforeMount
C mounted
P mounted
5.2 Vue 3(Composition API 日志)
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
export default {
name: 'Demo',
setup() {
console.log('setup')
onBeforeMount(() => console.log('onBeforeMount'))
onMounted(() => console.log('onMounted'))
onBeforeUpdate(() => console.log('onBeforeUpdate'))
onUpdated(() => console.log('onUpdated'))
onBeforeUnmount(() => console.log('onBeforeUnmount'))
onUnmounted(() => console.log('onUnmounted'))
},
}
setup() 是 Vue 3 组织逻辑的入口,它出现得很早;真正“DOM 可用”的时机还是 onMounted。
6. 面试高频问答
Q1:Vue 2 和 Vue 3 生命周期最大的区别是什么?
参考回答:
本质流程(挂载/更新/卸载)没变,主要差异是两点:一是 Vue 3 把 beforeDestroy/destroyed 改名为 beforeUnmount/unmounted;二是 Vue 3 引入 Composition API,setup() 在创建之初执行,很多原本写在 beforeCreate/created 的逻辑会前移到 setup(),并通过 onMounted/onUpdated/... 注册后续钩子。
Q2:为什么 created 里拿不到 DOM?应该放在哪?
参考回答:
created 阶段只完成了数据与依赖初始化,还没把虚拟 DOM 渲染成真实 DOM,所以 DOM 不存在或不完整。需要 DOM 的逻辑应该放 mounted(Vue 2)/onMounted(Vue 3),如果还要确保“DOM 更新后再读”,再配合 nextTick。
Q3:父子组件挂载时,mounted 谁先执行?
参考回答:
一般是 子组件先 mounted,父组件后 mounted。父组件会先走到 beforeMount,然后开始创建/挂载子组件,等子组件 mounted 完成后,父组件才会进入 mounted。
Q4:父子组件更新时,updated 谁先执行?
参考回答:
一般是 子组件先 updated,父组件后 updated。顺序通常是:父 beforeUpdate → 子 beforeUpdate → 子 updated → 父 updated。
Q5:Vue 3 里 setup() 和 onMounted() 分别适合做什么?
参考回答:
setup() 适合做“与 DOM 无关的初始化”,比如声明响应式状态、计算属性、方法、发起不依赖 DOM 的请求、注册生命周期钩子等;onMounted() 适合做“需要真实 DOM 的事情”,比如读写元素、初始化三方 UI/图表库、拿尺寸、绑定与 DOM 强相关的监听。
Q6:keep-alive 缓存组件为什么切换路由不触发销毁?那我该在哪刷新数据?
参考回答:
因为 keep-alive 的目标就是“缓存组件实例”,切换离开时不会卸载,只会触发 deactivated,回来触发 activated。需要“每次回来都刷新”的逻辑应该写在 activated/onActivated,而不是 mounted/onMounted。
Q7:beforeUpdate 和 updated 的区别是什么?有什么注意点?
参考回答:
beforeUpdate 时 DOM 还没被 patch,你读到的通常是旧 DOM;updated 时 DOM 已更新完成,适合读取更新后的 DOM。但 updated 里不要再无脑改触发渲染的状态,否则容易出现循环更新或性能问题。
Q8:清理定时器/事件监听应该写在哪个钩子?写 unmounted 行不行?
参考回答:
Vue 2 放 beforeDestroy,Vue 3 放 onBeforeUnmount 或 onUnmounted 都行。实践里更推荐在 “before” 阶段清理(beforeDestroy/onBeforeUnmount),因为此时组件还处在可控状态,清理更稳;但放在 unmounted 也能生效,只要确保一定会执行到(非 keep-alive 缓存切换)。