详解 Vue 的生命周期
原文主要介绍 Vue 2 Options API 的创建、挂载、更新、销毁阶段,并讨论父子组件顺序和请求放在哪个钩子。本文保留这条学习路线,修复生命周期顺序、
beforeMount作用、请求时机和 SSR 相关表述,并补充 Vue 3.5 Composition API 的对应钩子。
一、什么是组件生命周期
组件生命周期描述的是一个组件从创建、初始化响应式状态、挂载到 DOM、响应式更新,直到卸载的过程。
可以把它抽象为:
创建与初始化 → 挂载 → 更新 → 卸载
Vue 2 和 Vue 3 都有生命周期概念,但 API 名称有差异:
| 阶段 | Vue 2 Options API | Vue 3 Options API / Composition API |
|---|---|---|
| 创建前/创建后 | beforeCreate / created | Options API 仍有;setup() 承担组合式初始化 |
| 挂载 | beforeMount / mounted | beforeMount / mounted,或 onBeforeMount / onMounted |
| 更新 | beforeUpdate / updated | beforeUpdate / updated,或 onBeforeUpdate / onUpdated |
| 卸载 | beforeDestroy / destroyed | beforeUnmount / unmounted,或 onBeforeUnmount / onUnmounted |
Vue 3 将 destroy 命名为 unmount,因为组件是从应用树中卸载,而不是简单地“销毁一个对象”。Vue 3 已移除 beforeDestroy 和 destroyed 这两个旧名称。

二、生命周期执行顺序
一个同步组件的典型顺序如下。
2.1 Vue 2 Options API 创建和挂载
父 beforeCreate
父 created
父 beforeMount
子 beforeCreate
子 created
子 beforeMount
子 mounted
父 mounted
如果只列出原文重点的钩子,就是:
父 beforeMount
→ 子 created
→ 子 mounted
→ 父 mounted
但完整顺序还包括子组件的 beforeCreate、父组件的 beforeCreate/created 和子组件的 beforeMount,不能把简化列表当成完整生命周期。
2.2 Vue 3
Vue 3 的 Options API 顺序与上述总体关系相近;Composition API 则在 setup() 中注册 onMounted 等钩子:
<script setup>
import { onBeforeMount, onMounted } from 'vue'
onBeforeMount(() => {
console.log('组件即将挂载')
})
onMounted(() => {
console.log('组件已经挂载')
})
</script>
Composition API 生命周期钩子必须在 setup() 执行期间同步注册。不能等异步请求完成后才调用 onMounted,否则可能无法绑定到当前组件实例。

三、用 Vue 2 代码观察生命周期
原文的控制台示例被网页抓取成一行,下面整理为可读的 Vue 2 Options API 版本:
new Vue({
el: '#app',
data() {
return {
message: 'hello',
}
},
beforeCreate() {
console.log('beforeCreate', {
el: this.$el,
data: this.$data,
message: this.message,
})
},
created() {
console.log('created', {
el: this.$el, // 通常还没有挂载完成
data: this.$data,
message: this.message,
})
},
beforeMount() {
console.log('beforeMount', this.$el)
},
mounted() {
console.log('mounted', this.$el, this.$refs)
},
beforeUpdate() {
console.log('beforeUpdate:即将根据新状态更新 DOM')
},
updated() {
console.log('updated:组件 DOM 已完成本轮更新')
},
beforeDestroy() {
console.log('beforeDestroy:清理仍可访问的资源')
},
destroyed() {
console.log('destroyed:实例已从 Vue 管理树中移除')
},
})
注意:this.$el 在 created/beforeMount 阶段可能是占位元素或尚未指向最终 DOM,具体还和是否提供 el、模板编译方式有关;不要只凭某一个控制台截图断言所有场景都严格为 undefined。

四、各阶段适合做什么
4.1 beforeCreate / created
beforeCreate 很早执行,此时组件实例的 data、computed、methods 等初始化尚未完成,通常不适合执行业务逻辑。
created 执行时,Vue 2 的 data observation、computed、methods 和 watch 等已经完成,可以访问数据并发起不依赖 DOM 的请求:
export default {
data() {
return {
list: [],
}
},
async created() {
this.list = await fetchList()
},
}
Vue 3 <script setup> 中通常直接在顶层调用 composable 或执行初始化逻辑:
<script setup>
import { ref } from 'vue'
const list = ref([])
const loading = ref(true)
fetchList()
.then(data => {
list.value = data
})
.finally(() => {
loading.value = false
})
</script>
4.2 beforeMount / onBeforeMount
它们在组件即将进行首次 DOM 挂载时调用。此时组件已经完成响应式状态和 render 准备,但不应把它当成“获取 DOM 的安全时机”。需要 DOM 时,优先使用 mounted/onMounted。
beforeMount 也不等于“此时一定把模板编译成 render”。在构建项目中模板通常早已由编译器转换;运行时编译版本才可能在运行阶段编译模板。
4.3 mounted / onMounted
组件挂载后可以访问组件自己的 DOM 和 $refs:
<script setup>
import { onMounted, ref } from 'vue'
const input = ref(null)
onMounted(() => {
input.value?.focus()
})
</script>
<template>
<input ref="input" />
</template>
onMounted 表示同步子组件已经挂载,且组件自己的 DOM 已插入容器;异步组件、Suspense 或后续异步渲染需要根据具体边界处理,不能简单理解为整棵应用树都已完成。
适合做:
- 读取尺寸、位置等 DOM 信息;
- 初始化只在浏览器存在的第三方 DOM 库;
- 注册需要 DOM 目标的事件;
- 执行首屏动画。
如果任务不依赖 DOM,不能因为“mounted 更晚”就认为它一定比 created/setup 发请求更好。

4.4 beforeUpdate / onBeforeUpdate
响应式状态导致组件重新渲染、但 DOM 尚未应用本轮更新时调用。此时访问到的通常是旧 DOM 状态。
import { onBeforeUpdate } from 'vue'
onBeforeUpdate(() => {
// 读取本轮更新前的 DOM 状态
})
它适合少量需要在 DOM 更新前记录的信息。不要在这里无条件修改响应式状态,否则可能制造额外更新或循环。
4.5 updated / onUpdated
组件 DOM 完成本轮更新后调用:
import { onUpdated, nextTick } from 'vue'
onUpdated(() => {
// 可以读取更新后的 DOM
})
async function updateAndMeasure() {
state.value++
await nextTick()
// 只需要等待当前更新完成时,nextTick 往往更直接
}
updated 不应该用来无条件修改依赖自身的状态,否则可能形成更新循环。对于一次操作后的 DOM 读取,优先在操作函数中 await nextTick(),而不是把所有逻辑塞进全局 updated。
4.6 Vue 2 beforeDestroy / destroyed
Vue 2 中,beforeDestroy 适合清理定时器、全局事件、WebSocket、第三方实例和手动注册的监听器:
export default {
mounted() {
window.addEventListener('resize', this.handleResize)
this.timer = window.setInterval(this.refresh, 10_000)
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
clearInterval(this.timer)
this.socket?.close()
this.chart?.dispose()
},
}
destroyed 执行时,实例已经从 Vue 的父子关系和响应式观察中移除。某些普通 JavaScript 属性仍可能存在,但不要把它当作 DOM 或响应式资源仍然有效的保证。
4.7 Vue 3 beforeUnmount / onUnmounted
Vue 3 对应写法:
<script setup>
import { onMounted, onUnmounted } from 'vue'
function handleResize() {
// ...
}
onMounted(() => {
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
</script>
onUnmounted 适合清理组件创建的副作用。Vue 3.5 还可以使用 onWatcherCleanup 清理 watcher 中过期的请求,但它属于 watcher 清理 API,不是组件生命周期钩子的替代品。

五、父子组件生命周期顺序
对于同步父子组件,常见的顺序如下。
挂载阶段
父 beforeCreate
父 created
父 beforeMount
子 beforeCreate
子 created
子 beforeMount
子 mounted
父 mounted
父组件的 mounted 在其同步子组件都挂载后执行。原文“父组件挂载前,子组件已经挂载完成”的说法不准确:父 beforeMount 会先于子组件挂载,但父 mounted 会晚于同步子组件 mounted。
更新阶段
当父组件更新导致子树也更新时,常见顺序为:
父 beforeUpdate
子 beforeUpdate
子 updated
父 updated
具体顺序还会受到更新来源、组件是否真的有变化、scheduler 批处理和异步边界影响;不要用一个子组件示例推断所有更新任务的全局顺序。
卸载阶段
Vue 2:
父 beforeDestroy
子 beforeDestroy
子 destroyed
父 destroyed
Vue 3 对应为:
父 beforeUnmount
子 beforeUnmount
子 unmounted
父 unmounted
如果组件由 <KeepAlive> 缓存,切换时通常触发 activated/deactivated(或 onActivated/onDeactivated),而不是立即卸载;这与真正从组件树移除的 unmount 不同。

可以用一个子组件日志观察局部顺序:
export default {
created() {
console.log('list created')
},
mounted() {
console.log('list mounted')
},
beforeUpdate() {
console.log('list before update')
},
updated() {
console.log('list updated')
},
beforeDestroy() {
console.log('list beforeDestroy')
},
destroyed() {
console.log('list destroyed')
},
}
Vue 3 Composition API 则写成 onMounted、onUpdated 和 onUnmounted。
六、请求放在哪个生命周期更合适
“请求一定放在 mounted 比 created 好”并不是通用结论。选择标准是请求是否依赖 DOM,以及项目是否需要 SSR:
| 场景 | 合适位置 |
|---|---|
| Vue 2,数据请求不依赖 DOM | created 常见,也可以在路由进入守卫或其他服务层请求 |
| Vue 2,请求完成后必须操作 DOM | 请求可在 created,DOM 操作放到 mounted/$nextTick |
| Vue 3,普通组件初始化 | setup() 顶层、composable 或 onBeforeMount 前的业务逻辑 |
| Vue 3,仅浏览器可用的 API | onMounted |
| Nuxt 4,需要 SSR/预取/缓存 | useFetch、useAsyncData 或服务端数据加载方案 |
| 监听参数变化重新请求 | watch/watchEffect,并清理过期请求 |
请求本身通常不需要等待 DOM。把请求延后到 mounted 可能反而推迟数据加载;只有请求依赖浏览器 API、组件已挂载状态或第三方 DOM 实例时,才应该放在 onMounted。
Vue 3 SSR 中,onMounted、onUpdated、onUnmounted 等仅客户端钩子不会在服务端执行;需要服务端预取时,应使用 onServerPrefetch 或 Nuxt 的数据获取 API。setup() 和 Options API 的 created 可能在服务端执行,因此其中不要直接访问 window、document。

七、其他常用生命周期能力
7.1 <KeepAlive>
缓存组件切换时使用:
import { onActivated, onDeactivated } from 'vue'
onActivated(() => {
// 组件从缓存中重新激活
})
onDeactivated(() => {
// 组件进入缓存或被卸载
})
这两个钩子适合暂停轮询、重新获取可见数据或管理缓存页面资源。
7.2 错误捕获
Options API 可以使用 errorCaptured,Composition API 可以使用 onErrorCaptured 捕获后代组件的错误,并决定是否继续向上传播。它不是全局异常监控的完整替代方案,生产环境还应接入统一错误上报。
7.3 调试响应式依赖
Vue 3 开发环境提供 onRenderTracked 和 onRenderTriggered,帮助定位组件为什么收集或触发了某个响应式依赖。它们适合调试,不应作为业务逻辑依赖。

总结
- 生命周期描述组件从创建、挂载、更新到卸载的过程;
- Vue 2 使用
beforeDestroy/destroyed,Vue 3 使用beforeUnmount/unmounted; - Vue 3 Composition API 在
setup()中注册onMounted等钩子; - 父子组件顺序通常是父 beforeMount、子完整挂载、父 mounted;更新和卸载也遵循由外到内再由内到外的总体关系;
created/setup适合做不依赖 DOM 的数据初始化,mounted/onMounted适合 DOM 相关操作;- 请求不必为了“更晚”而放到 mounted,SSR 和 Nuxt 还要使用服务端可执行的数据获取方案;
- 组件卸载前应清理定时器、事件、连接和第三方实例;
- KeepAlive 切换会触发 activated/deactivated,不等同于真正卸载。
参考资料
- Vue 3:Lifecycle Hooks
- Vue 3:Composition API 生命周期
- Vue 3:SSR 数据预取
- Vue 2:Instance Lifecycle Hooks
- Vue 3 迁移指南:生命周期 API
- Nuxt 4:Data Fetching
原文作者:是乃德也是 Ned。原文关于 Vue 2 生命周期阶段、控制台观察、父子顺序、资源清理和请求时机的主线予以保留;抓取噪声、生命周期顺序遗漏、mounted 一定更适合请求、SSR 表述和 Vue 2/3 API 混淆已修正或补充。