Vue 3 生命周期完整指南
Category(分类): Vue Status: 已核对
最近开源了一个 Vue 组件,还不够完善,欢迎大家一起完善,也希望大家能给个 star 支持一下,谢谢各位。
GitHub 地址:github.com/qq449245884/vue-okr-tree
Vue 2 和 Vue 3 的生命周期主线非常相似。Vue 3 仍完整支持 Options API,并没有要求 Composition API 替代它;但 Vue 2 的 beforeDestroy、destroyed 在标准 Vue 3 中改名为 beforeUnmount、unmounted,迁移时并非“完全不用改代码”。
本文主要内容:
- Vue 生命周期钩子有哪些
- 在 Options API 中使用生命周期钩子
- 在 Composition API 中使用生命周期钩子
- 从 Vue 2 更新到 Vue 3
- 创建、挂载、更新与卸载
- KeepAlive 的激活与停用
- 错误、调试与 SSR 钩子
- 副作用清理
Vue 生命周期钩子有哪些
注意:下图是旧版简化示意图。 图中把
setup标在beforeCreate、created附近只是为了对照创建阶段,不表示会分别调用两次。每个组件的setup()只调用一次,并且在所有 Options API 生命周期钩子之前执行。

可以先按四个主要阶段理解组件:创建、挂载、更新、卸载;每个挂载、更新、卸载阶段都有前后钩子。但“完整”API 还包括 KeepAlive、错误捕获、开发调试和 SSR 数据预取。
| 阶段 | Options API | Composition API | SSR |
|---|---|---|---|
| 初始化 | beforeCreate、created | 没有对应的 onXxx;setup() 是更早的初始化入口 | 执行 |
| 挂载 | beforeMount、mounted | onBeforeMount、onMounted | 不执行 |
| 更新 | beforeUpdate、updated | onBeforeUpdate、onUpdated | 不执行 |
| 卸载 | beforeUnmount、unmounted | onBeforeUnmount、onUnmounted | 不执行 |
| 缓存 | activated、deactivated | onActivated、onDeactivated | 不执行 |
| 错误 | errorCaptured | onErrorCaptured | 视错误发生阶段而定 |
| 开发调试 | renderTracked、renderTriggered | onRenderTracked、onRenderTriggered | 不执行 |
| 服务端预取 | serverPrefetch | onServerPrefetch | 仅 SSR,等待返回的 Promise |
在 Options API 中使用生命周期钩子
Options API 钩子直接写在组件选项中,无需导入:
<script>
export default {
mounted() {
console.log('mounted!')
},
updated() {
console.log('updated!')
}
}
</script>
在 Composition API 中使用生命周期钩子
组合式钩子从 vue 导入,并且必须在 setup() 同步执行期间注册。异步 setup() 应在第一个 await 前注册,否则可能无法关联当前组件实例。
<script setup>
import { onMounted, onUnmounted } from 'vue'
onMounted(() => {
console.log('mounted in Composition API!')
})
onUnmounted(() => {
console.log('unmounted in Composition API!')
})
</script>
将 Vue 2 生命周期代码更新到 Vue 3
下面是迁移记忆表,不表示 setup() 与创建钩子时序完全相同:
- Vue 2
beforeCreate、created:Vue 3 Options API 仍保留;组合式逻辑放在setup()。 beforeMount→onBeforeMountmounted→onMountedbeforeUpdate→onBeforeUpdateupdated→onUpdated- Vue 2
beforeDestroy→ Vue 3beforeUnmount/onBeforeUnmount - Vue 2
destroyed→ Vue 3unmounted/onUnmounted activated→onActivateddeactivated→onDeactivatederrorCaptured→onErrorCaptured
setup() 实际在所有 Options API 钩子(包括 beforeCreate)之前运行,且其中没有组件 this。
深入了解每个生命周期钩子
beforeCreate()——Options API
此时 props 已解析,但 data、computed、methods 和 watch 等状态选项尚未完成,不应依赖 DOM:
export default {
data() {
return { val: 'hello' }
},
beforeCreate() {
console.log(this.val) // undefined
}
}
created()——Options API
此时响应式 data、computed、methods 和 watch 已就绪,但没有 DOM:
export default {
data() {
return { val: 'hello' }
},
created() {
console.log(`Value of val is: ${this.val}`)
}
}
旧文建议“一律在 created 请求 API”过于绝对:created 在 SSR 也会执行,而且服务端创建的定时器等副作用可能没有卸载钩子清理。数据获取应依据 SSR 框架或 serverPrefetch 设计;只属于浏览器的副作用放在 mounted。
Composition API 的创建阶段
Composition API 没有 onBeforeCreate、onCreated。初始化逻辑直接写在 setup() 或 <script setup>:
<script setup>
import { ref } from 'vue'
const val = ref('hello')
console.log(`Value of val is: ${val.value}`)
</script>
beforeMount() 与 onBeforeMount()
首次渲染前调用,组件 DOM 尚未创建。Vue 3 是 $el(单数),但此时不应依赖它;模板 ref 通常也是 null:
<script setup>
import { onBeforeMount, ref } from 'vue'
const root = ref(null)
onBeforeMount(() => {
console.log(root.value) // 通常是 null
})
</script>
<template>
<div ref="root">Hello World</div>
</template>
mounted() 与 onMounted()
挂载后可以访问自身 DOM。它保证自身 DOM 已插入父容器,并等待同步子组件挂载;不保证异步组件、<Suspense> 内组件、图片、字体或网络资源已经完成,也不会在 SSR 中调用。
Options API 通过 this.$el 访问组件根 DOM:
export default {
mounted() {
console.log(this.$el)
}
}
Composition API 则通常使用模板 ref:
<script setup>
import { onMounted, ref } from 'vue'
const root = ref(null)
onMounted(() => {
console.log(root.value)
})
</script>
<template>
<div ref="root">Hello World</div>
</template>
两种写法表达的是相同的挂载时机。等待某次状态变更后的 DOM,应使用 await nextTick();等待资源则使用资源自身的 Promise 或 load 事件。
beforeUpdate() 与 onBeforeUpdate()
响应式状态变化、DOM patch 前调用,可读取更新前 DOM。不要在其中无条件修改参与当前渲染的状态,否则可能递归更新。旧文用它统计编辑次数会导致循环,应该改用 watch。
updated() 与 onUpdated()
组件 DOM 更新后调用。父组件的 updated 在子组件之后运行;它是任意组件更新的通知,不适合精确监听某一个值。官方明确警告:不要在 updated 中修改会触发自身渲染的状态。
下面保留原“统计编辑次数”主题,但改为 watcher:
<script setup>
import { ref, watch } from 'vue'
const val = ref(0)
const count = ref(0)
watch(val, () => {
count.value++
})
</script>
<template>
<p>{{ val }} | edited {{ count }} times</p>
<button @click="val = Math.floor(Math.random() * 101)">
Click to Change
</button>
</template>
若要在特定状态变化后读取 DOM,可用 watch(source, callback, { flush: 'post' }) 或在赋值后 await nextTick(),而不是把业务都放进 updated。
beforeUnmount() 与 onBeforeUnmount()
卸载前实例仍然可用,适合拆除依赖实例或 DOM 的插件。原来的 onBeforeUnmounted 是错误名称。
export default {
mounted() {
window.addEventListener('resize', this.someMethod)
},
beforeUnmount() {
window.removeEventListener('resize', this.someMethod)
},
methods: {
someMethod() {
// 处理窗口变化
}
}
}
<script setup>
import { onMounted, onBeforeUnmount } from 'vue'
function handleResize() {
// 处理窗口变化
}
onMounted(() => window.addEventListener('resize', handleResize))
onBeforeUnmount(() => window.removeEventListener('resize', handleResize))
</script>
unmounted() 与 onUnmounted()
卸载后,子组件已卸载,组件的 render effect,以及在 setup() 中同步创建并绑定组件的 computed、watch、watchEffect 会停止。但手动的定时器、DOM 监听器、WebSocket、第三方实例仍需显式清理:
Options API 和 Composition API 都可以在卸载后执行收尾逻辑。下面先看 Options API:
export default {
mounted() {
this.timer = window.setInterval(() => console.log('tick'), 1000)
},
unmounted() {
window.clearInterval(this.timer)
}
}
对应的 Composition API 写法:
<script setup>
import { onMounted, onUnmounted } from 'vue'
let timer
onMounted(() => {
timer = window.setInterval(() => console.log('tick'), 1000)
})
onUnmounted(() => {
window.clearInterval(timer)
})
</script>
异步回调里创建的 watcher 不一定绑定组件,应保存其停止句柄。请求副作用可通过 watcher 回调第三参数 onCleanup 取消;Vue 3.5+ 也有必须在同步阶段调用的 onWatcherCleanup()。
activated()、deactivated() 与 KeepAlive
<KeepAlive> 切换离开的缓存实例会停用而非卸载:
<template>
<button @click="tabName = Tab1">Tab 1</button>
<button @click="tabName = Tab2">Tab 2</button>
<KeepAlive>
<component :is="tabName" />
</KeepAlive>
</template>
<script setup>
import { shallowRef } from 'vue'
import Tab1 from './Tab1.vue'
import Tab2 from './Tab2.vue'
const tabName = shallowRef(Tab1)
</script>
Tab1.vue 中放一个输入框,就能直观看到切换到 Tab 2 再返回时,输入内容仍被缓存:
<template>
<div>
<h2>Tab 1</h2>
<input type="text" placeholder="this content will persist!">
</div>
</template>
<script setup>
import { onActivated, onDeactivated } from 'vue'
onActivated(() => console.log('Tab 1 Activated'))
onDeactivated(() => console.log('Tab 1 Deactivated'))
</script>
缓存组件内部也可以用这两个钩子管理轮询、订阅等资源:
<script setup>
import { onActivated, onDeactivated } from 'vue'
onActivated(() => {
// 首次挂载及从缓存重新插入时调用:恢复轮询/订阅
})
onDeactivated(() => {
// 移入缓存以及最终卸载时调用:暂停资源
})
</script>
v-show 不会触发这两个钩子。资源必须暂停时,不能只依靠视觉隐藏。

errorCaptured 与 onErrorCaptured
它们捕获后代组件在渲染、事件处理、生命周期、setup()、watcher 等阶段抛出的错误,参数为错误、暴露的组件实例和来源信息。返回 false 可阻止错误继续传播,但应避免在错误处理本身再次渲染同一故障内容。生产环境还可以配置 app.config.errorHandler 统一上报。
export default {
errorCaptured(error, instance, info) {
console.error(error, info)
return false
}
}
Vue 3 开发调试钩子
renderTracked / onRenderTracked 在渲染 effect 跟踪依赖时调用;renderTriggered / onRenderTriggered 在依赖变化触发渲染时调用。二者只在开发模式生效,不在 SSR 调用,也不应承载业务逻辑:
下面保留原文的购物车交互例子。首次渲染读取 cart 时会调用 renderTracked;点击按钮写入 cart 时会调用 renderTriggered:
<div id="app">
<button @click="addToCart">Add to cart</button>
<p>Cart({{ cart }})</p>
</div>
const app = Vue.createApp({
data() {
return { cart: 0 }
},
renderTracked({ key, target, type }) {
console.log('tracked', { key, target, type })
// 首次渲染会记录对 cart 的 get 操作
},
renderTriggered({ key, target, type }) {
console.log('triggered', { key, target, type })
// 点击按钮后会记录对 cart 的 set 操作
},
methods: {
addToCart() {
this.cart += 1
}
}
})
app.mount('#app')
调试事件对象的 target 是发生响应式操作的对象,key 是被访问或修改的键,type 则描述 get、set、add、delete 等操作类型。这样既能看到“渲染跟踪了什么”,也能看到“什么变化触发了重渲染”。
SSR:哪些钩子会运行
SSR 没有动态挂载与更新,因此 mounted、updated、卸载、KeepAlive 和渲染调试钩子不会在服务端执行;setup()、beforeCreate、created 会执行。不要在这些通用初始化位置无条件访问 window、document,也不要启动只能由客户端卸载钩子清理的定时器。
服务端预取可以使用 serverPrefetch / onServerPrefetch,或 Nuxt 等框架的数据 API:
<script setup>
import { onServerPrefetch, ref } from 'vue'
const article = ref(null)
onServerPrefetch(async () => {
article.value = await fetch('https://example.com/api/article').then(r => r.json())
})
</script>
总结
无论选择 Options API 还是 Composition API,都应按副作用所依赖的资源选择钩子,而不是为了“使用新 API”机械替换。DOM 工作放在挂载后,精确状态变化用 watch,手动资源在卸载时清理,缓存组件用激活/停用管理,SSR 数据使用专门入口。
官方参考
- Lifecycle Hooks
- Options: Lifecycle
- Composition API: Lifecycle Hooks
- KeepAlive
- SSR Component Lifecycle Hooks
- Watchers:副作用清理
~完,我是刷碗智,我要去刷碗了,骨的白。
作者:王大冶
链接:https://juejin.cn/post/6945606524987244558 来源:稀土掘金。著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。