Vue 生命周期作为 Vue 框架的核心概念之一,对于开发者理解和掌控组件的运行过程起着至关重要的作用。深入了解 Vue 生命周期,能够帮助开发者在合适的时机执行特定操作,从而优化应用的性能和交互体验。
一、Vue 实例基础
Vue 实例是什么
Vue 实例是 Vue 应用的核心载体,本质上是通过 Vue 构造函数创建出来的一个对象。它管理着数据响应式、模板渲染以及与 DOM 的交互逻辑。
每个 Vue 应用至少有一个根实例,通过配置选项(如 data、methods)定义其行为。在实际开发中,Vue 实例会将 JavaScript 数据与 HTML 模板结合,最终渲染到页面指定的 DOM 元素上。
在 Vue 2 中,使用 new Vue() 构造函数创建实例,而在 Vue 3 里,则是通过 createApp 函数来创建。
Vue 2 创建实例
在 Vue 2 中,通过 new Vue() 构造函数创建实例,并传入配置对象:
const Vue = require('vue');
const app = new Vue({
data: {
message: 'Hello, Vue 2!'
},
methods: {
sayHello() {
console.log(this.message);
}
}
});
el 选项与挂载
el 是 Vue 2 实例的一个关键选项,用于指定实例挂载的 DOM 元素,值可以是 CSS 选择器(如 '#app')或 DOM 元素对象。
-
直接挂载:
const app = new Vue({ el: '#app', // 实例创建时直接挂载到 id="app" 的元素 data: { message: 'Direct mount' } }); -
延迟挂载:
若创建实例时未指定
el,可通过$mount方法手动挂载:const app = new Vue({ data: { message: 'Delayed mount' } }); app.$mount('#app'); // 调用 $mount 后实例才挂载到 DOM
Vue 3 创建实例
Vue 3 采用了组合式 API,通过 createApp 函数来创建应用实例。示例如下:
import { createApp, ref } from 'vue';
const app = createApp({
setup() {
const message = ref('Hello, Vue 3!');
const sayHello = () => {
console.log(message.value);
};
return {
message,
sayHello
};
}
});
Vue 3 中无 el 选项,需通过 mount 方法指定挂载目标:
app.mount('#app'); // 将实例挂载到 id="app" 的元素
在 Vue 3 里,createApp 函数返回一个应用实例,之后可以使用 mount 方法将其挂载到 DOM 元素上。
二、Vue 生命周期概述
Vue 生命周期指的是 Vue 实例从创建、初始化、挂载到 DOM、数据更新,再到卸载销毁的这一系列过程。在这个过程中的不同阶段,Vue 提供了对应的钩子函数,方便开发者在特定阶段执行自定义代码。
图片来源:官方文档
从图中可以清晰地看到 Vue 实例从开始创建到最终销毁所经历的各个关键节点。
什么是生命周期钩子
生命周期钩子是 Vue 在组件实例从创建到销毁的各个阶段自动调用的特殊函数。它就像是在 Vue 实例生命周期的特定时刻设置的 “钩子点”,开发者可以将自定义代码 “挂” 在这些点上,让代码在对应的阶段执行。
钩子的本质与原理
从本质上讲,Vue 内部维护了一套状态管理机制,在组件实例的不同状态变化时,会去检查是否有开发者定义的对应钩子函数。如果有,就执行这些函数。
- 例如,当组件准备将模板渲染为真实 DOM 时,Vue 会检查是否定义了
onBeforeMount钩子函数,如果定义了,就会调用该函数。
三、Vue2 生命周期
(一)创建阶段
Vue 实例的创建阶段是从实例被初始化到挂载到 DOM 之前的过程,核心目的是完成实例的配置解析与数据初始化。
beforeCreate
-
在 Vue 实例刚被创建时,beforeCreate 钩子函数会被调用。
-
此时,Vue 实例仅完成了最基础的初始化工作,像实例的一些内部属性和方法被创建,但实例的
data、computed、watch、methods等选项还未进行初始化。 -
由于上述选项尚未初始化,所以无法访问
data里的数据,也不能调用methods中的方法。同时,由于还未进行数据劫持,数据的响应式系统也未启动。
-
-
此阶段可执行的操作有限,但在某些特殊场景下,它可用于添加全局配置。
created
当 Vue 实例完成数据劫持(通过 Object.defineProperty 或 Proxy 实现响应式转换)以及属性和方法的初始化后,created钩子函数被触发。
-
此阶段可通过
this访问以下内容:- 组件内部:
data:已完成响应式处理,数据可进行读写操作。methods:组件内定义的方法,可以直接调用。computed:基于响应式数据的计算属性已完成初始化,访问时会自动触发依赖收集。watch:监听器已完成注册,虽不能直接调用监听器函数,但修改被监听的数据会触发相应回调。
- 依赖注入:
- 通过
this访问以inject选项声明的依赖,从而直接获取父组件或祖先组件经provide提供的值。
- 通过
- 全局扩展:
- 当使用全局混入(
Vue.mixin)或者插件(Vue.use)时,它们所添加的属性和方法会合并到当前组件实例中,此时可通过this来访问这些属性和方法。
- 当使用全局混入(
- 内置功能:
$options:可用于访问组件的原始选项配置,例如this.$options.propsData能够获取props的初始值。$emit:用于触发自定义事件。- 其他实例方法:通过
$root访问根实例,借助$parent访问父组件实例等。
- 组件内部:
-
此阶段需要注意的:
-
无法操作 DOM:组件尚未挂载到真实 DOM 树,
this.$el为undefined,直接操作 DOM(如document.getElementById)会报错,需在mounted钩子中进行。 -
异步操作无法依赖 DOM 状态:例如在
created中发起异步请求并试图根据返回数据更新 DOM 时,因 DOM 未渲染,无法确保更新效果正确。
-
(二)挂载阶段
Vue 实例的挂载阶段,是指从模板编译完成到实例被渲染并插入真实 DOM 树的过程。在此阶段,Vue 会将编译好的模板转化为真实的 DOM 元素,并将其插入到指定的挂载点。
beforeMount
当 Vue 实例准备将编译好的模板挂载到真实 DOM 时,beforeMount钩子函数被调用。此时:
-
模板状态:模板已完成编译,生成了虚拟 DOM 树,但尚未被渲染为真实的 DOM 元素并插入到页面中。
-
可操作内容:在此阶段,可以对数据或其他实例选项进行最后的调整。因为此时对数据的修改仍会触发虚拟 DOM 的重新计算与更新,最终反映到真实 DOM 上。
- 对于数据,可检查准确性(如缺失、类型错误)、调整排序(升/降序等)或调整关联数据关系。
- 对于实例选项,可以检查各项配置是否符合业务需求,比如某些功能的开启或关闭状态、参数的设置等,若不合适则进行调整。
-
不可操作内容:由于真实 DOM 还未挂载,使用
this.$el访问 DOM 元素时,它可能只是一个注释节点或者未渲染的占位符,不能进行实际的 DOM 操作。
mounted
当 Vue 实例成功将模板渲染为真实 DOM 元素,并插入到文档中指定的挂载点后,mounted钩子函数被触发。此时:
-
DOM 状态:实例已经挂载到真实 DOM 树,
this.$el指向了挂载的 DOM 元素。 -
可操作内容
- 可执行DOM 操作:例如获取特定元素的尺寸或位置:
export default { mounted() { const element = document.getElementById('my - element'); console.log('元素宽度:', element.offsetWidth); } };-
第三方插件初始化:许多第三方插件(如轮播图插件 swiper、图表插件 Echarts 等)需要在 DOM 元素渲染完成后进行初始化。
-
数据请求 :虽然在
created阶段也可以发起数据请求,但在mounted阶段进行数据请求,有时可以确保在 DOM 渲染完成后,根据数据更新页面时不会出现闪烁或布局抖动的情况,尤其是当数据展示依赖特定的 DOM 结构时。
-
注意:在
mounted钩子中进行异步操作时,要注意避免频繁触发重绘和回流。- 如果在
mounted中通过定时器不断修改 DOM 元素的样式,可能会导致性能问题。可以考虑使用requestAnimationFrame等优化手段来批量处理 DOM 更新。
- 如果在
(三)更新阶段
Vue 实例的更新阶段,是指当响应式数据发生变化时,从虚拟 DOM 重新计算到真实 DOM 更新完成的过程。
beforeUpdate
当 Vue 检测到响应式数据(如 data 中的属性)发生变化,且即将重新渲染组件时,beforeUpdate 钩子函数被调用。此时:
-
数据与 DOM 状态:
- 数据已更新为最新值,但真实 DOM 尚未同步更新,仍保留着上一次渲染的状态;
- 虚拟 DOM 正在重新计算差异(Diff 算法),尚未应用到真实 DOM。
-
可执行操作:
- 数据校验:可在数据更新前验证新值是否符合业务规则。例如,表单组件中校验输入值是否合法,若不合法可回滚数据:
export default { data() { return { inputValue: '' }; }, beforeUpdate() { if (this.inputValue.length > 10) { // 限制输入长度,超出则恢复原值 this.inputValue = this.inputValue.slice(0, 10); } } };- 状态记录:记录更新前的 DOM 状态(如元素样式、滚动位置),便于后续对比或恢复。
-
注意:
虽然在该阶段可以访问更新前的 DOM,但要避免直接修改 DOM。因为此时修改会被 Vue 在下一轮渲染时覆盖,可能导致性能浪费或逻辑冲突。
updated
当 Vue 完成虚拟 DOM 差异计算,并将更新应用到真实 DOM 后,updated 钩子函数被触发。此时:
-
DOM 状态:真实 DOM 已完全同步为最新数据对应的结构和内容,
this.$el指向更新后的 DOM 元素。 -
可执行操作:
- 进行 DOM 操作:例如,根据更新后的 DOM 重新初始化插件、获取动态元素尺寸:
export default { data() { return { showChart: false }; }, methods: { initChart() { // 使用第三方图表库(如 ECharts)初始化图表 const chartDom = this.$el.querySelector('.chart-container'); // ... } }, updated() { if (this.showChart) { this.initChart(); } } };- 异步操作:在 DOM 更新后执行延迟任务(如动画触发)。
-
注意事项:
-
避免循环更新:不要在
updated钩子内修改响应式数据,否则会再次触发beforeUpdate和updated,导致死循环。 -
性能考量:频繁在
updated中操作 DOM 可能影响性能,建议合并多次更新或使用nextTick批量处理。
-
(四)销毁阶段
Vue 实例的销毁阶段,是指从实例主动或被动触发销毁指令,到所有关联资源被释放的过程。
beforeDestroy
触发时机:
-
手动调用销毁方法:当在组件内部调用
this.$destroy()方法时,beforeDestroy钩子会立即触发,这通常用于手动控制组件的销毁过程。 -
父组件销毁:若父组件被销毁,其所有子组件也会随之被销毁。
-
路由切换:在使用 Vue Router 进行路由切换时,如果当前组件所在的路由被切换到其他路由,且该组件不在新路由的渲染范围内,那么该组件会被销毁,进而触发
beforeDestroy钩子。 -
动态组件移除:使用动态组件(如
<component :is="currentComponent"></component>)时,当currentComponent的值改变,旧组件会被卸载,此时旧组件的beforeDestroy钩子会被触发。 -
条件渲染变更:当使用
v-if指令控制组件的显示与隐藏时,若条件变为false,组件会从 DOM 中移除并销毁。
此时:
-
实例状态:
- 实例尚未真正销毁,仍可访问所有属性(如
data、methods等)和生命周期钩子。 - 但 Vue 已开始停止响应式系统的追踪,新的数据变化不会触发更新。
- 实例尚未真正销毁,仍可访问所有属性(如
-
必做操作:
- 资源清理:手动清除定时器(
setInterval/setTimeout)、WebSocket 连接、第三方库实例等。例如:
export default { data() { return { timerId: null, socket: null }; }, created() { this.timerId = setInterval(() => { console.log('定时任务'); }, 1000); this.socket = new WebSocket('ws://example.com'); }, beforeDestroy() { clearInterval(this.timerId); // 清除定时器 this.socket.close(); // 关闭 WebSocket 连接 } };- 事件解绑:移除自定义或 DOM 事件监听器,避免内存泄漏。例如:
export default { mounted() { window.addEventListener('resize', this.handleResize); }, beforeDestroy() { window.removeEventListener('resize', this.handleResize); }, methods: { handleResize() { // 窗口 resize 逻辑 } } }; - 资源清理:手动清除定时器(
-
可执行操作:
可进行数据备份或状态上报,但避免修改响应式数据(因即将销毁,修改无意义)。
destroyed
当 Vue 完成实例销毁流程,包括移除所有子组件、解绑事件、停止响应式追踪后,destroyed 钩子函数被触发。此时:
-
实例状态:
- 实例已彻底销毁,所有与 Vue 相关的资源(如
data、computed)均不可访问。 - 但 DOM 元素仍保留在文档中(除非通过其他逻辑手动移除)。
- 实例已彻底销毁,所有与 Vue 相关的资源(如
-
典型用途:
- 日志记录:记录组件销毁时间或状态,用于调试或监控。
export default { destroyed() { console.log('组件已销毁'); // 可上报至监控系统 // analytics.track('ComponentDestroyed', { name: 'MyComponent' }); } };- 手动 DOM 清理(可选) :若组件创建了额外的 DOM 元素(如弹窗、临时容器),可在此移除:
export default { data() { return { customDiv: null }; }, mounted() { this.customDiv = document.createElement('div'); document.body.appendChild(this.customDiv); }, destroyed() { if (this.customDiv) { this.customDiv.parentNode.removeChild(this.customDiv); } } }; -
注意事项:
避免访问实例属性(如this.data)或调用实例方法(如this.methods),因为实例已失效,可能导致报错。
四、Vue3 生命周期
(一)初始化阶段
Vue3 的初始化阶段以 setup 函数为核心入口,替代了 Vue2 中 beforeCreate 和 created 的功能,同时新增了 onRenderTracked 和 onRenderTriggered 用于调试响应式依赖。
setup
setup 函数在组件创建之初执行,早于其他生命周期钩子,是组合式 API 的起点。
-
特性:
- 无法访问
this(需通过getCurrentInstance获取实例上下文)。 - 可用
ref、reactive创建响应式数据,watch监听变化,computed定义计算属性。 - 接收
props和context参数,用于获取组件属性和触发事件。
- 无法访问
import { ref, onMounted } from 'vue';
export default {
setup(props, { emit }) {
const count = ref(0);
const increment = () => {
count.value++;
};
onMounted(() => {
console.log('组件已挂载');
});
return {
count,
increment
};
}
};
(二)挂载阶段
Vue3 的挂载钩子名称调整为 onBeforeMount 和 onMounted,逻辑与 Vue2 对应钩子相似,但需通过组合式 API 调用。
onBeforeMount
在模板即将渲染为真实 DOM 前触发,可用于最后阶段的数据修正。
-
操作场景:
- 动态修改响应式数据(仍会触发虚拟 DOM 的重新计算);
- 检查异步加载的资源是否准备就绪。
onMounted
组件成功挂载到 DOM 后触发,可操作真实 DOM 或初始化第三方库。
import { onMounted } from 'vue';
export default {
setup() {
onMounted(() => {
const el = document.getElementById('my-element');
console.log('元素宽度:', el.offsetWidth);
});
return {};
}
};
(三)更新阶段
更新钩子同样调整为 onBeforeUpdate 和 onUpdated,需结合组合式 API 使用。
onBeforeUpdate
数据更新导致组件重新渲染前触发,可用于记录旧状态。
import { ref, onBeforeUpdate } from 'vue';
export default {
setup() {
const text = ref('初始文本');
onBeforeUpdate(() => {
const oldText = text.value;
console.log(`即将从 "${oldText}" 更新`);
});
return { text };
}
};
onUpdated
DOM 更新完成后触发,可执行依赖最新 DOM 的操作。
- 注意事项:避免在此期间修改响应式数据,防止循环更新。
(四)卸载阶段
Vue3 将销毁逻辑聚焦于组件从 DOM 中移除的过程,使用 onBeforeUnmount 和 onUnmounted 替代 Vue2 的 beforeDestroy 和 destroyed。
onBeforeUnmount
组件即将从 DOM 中卸载时触发,用于清理资源。
-
必做操作:
- 清除定时器(
clearInterval)。 - 解绑 DOM 事件(
removeEventListener)。 - 销毁第三方实例(如 ECharts 图表)。
- 清除定时器(
onUnmounted
当组件成功从 DOM 移除后,onUnmounted 钩子会被触发。此时所有相关的响应式作用已停止,响应式数据修改不会触发更新,计算属性和侦听器也自动清理完毕。
这是由于 Vue 自动清理了渲染副作用(如虚拟 DOM 绑定),使得 DOM 事件监听器和指令失效,属性修改不再触发 DOM 更新,组件也不再监听 DOM 事件,之前绑定的 DOM 操作和指令均不再起作用。
-
用途:
- 必须操作:手动清除非响应式资源,如定时器、自定义 DOM 事件、WebSocket 连接等,以此避免内存泄漏。
- 建议操作:释放内存,例如清空全局变量引用;记录组件卸载日志,方便后续调试和监控。
- 避免操作:不要修改响应式数据或调用已清理的侦听器,因为此时这些操作不会产生预期的效果。
(五)其他生命周期钩子
onErrorCaptured
-
触发时机:当捕获到一个来自子孙组件的错误时被调用。它可以捕获到组件渲染期间、生命周期钩子函数执行期间以及
watch回调函数执行期间抛出的错误。 -
用途:在组件内部捕获子孙组件抛出的错误,进行统一的错误处理,避免错误直接导致整个应用崩溃。同时可以记录错误日志,方便后续调试。
-
返回值:可以返回
false以阻止错误继续向上传播。
onActivated / onDeactivated(keep-alive 专用)
-
触发时机:
onActivated:被keep-alive缓存的组件重新激活时调用;onDeactivated:组件进入缓存时调用。
-
应用场景:
- 缓存表单组件时保存输入状态;
- 暂停 / 恢复组件内的动画或定时器。
onRenderTracked / onRenderTriggered(调试专用)
-
用途:追踪响应式数据的依赖收集和触发情况,便于排查性能问题。
onRenderTracked:组件渲染期间,当响应式数据的依赖被收集时触发,借助它能清晰知晓组件在渲染时依赖了哪些响应式数据,帮助找出可能因依赖不必要数据而导致的频繁渲染问题。onRenderTriggered:响应式数据发生变化,从而触发组件重新渲染时被调用,通过它可以明确是哪些响应式数据的变化引发了组件的重新渲染,帮助排查组件频繁重新渲染的性能问题。
import { onRenderTracked, onRenderTriggered } from 'vue'; export default { setup() { const state = { count: 0 }; onRenderTracked((event) => { console.log('依赖被追踪:', event); }); onRenderTriggered((event) => { console.log('依赖被触发:', event); }); return { state }; } }; -
仅用于调试:
onRenderTracked/onRenderTriggered是专门为调试设计的钩子,不应该在生产环境中使用。因为它会增加额外的性能开销,并且在生产环境中我们通常不需要这些调试信息。
onServerPrefetch
-
触发时机:在服务端渲染期间,组件实例在服务器上被创建后,渲染之前调用。此钩子只会在服务端渲染时触发,在客户端不会触发。
-
用途:适合在服务端获取数据,以便在渲染组件之前就把数据准备好。这样可以避免在客户端再进行额外的数据请求,有助于提升首屏加载速度,并且对搜索引擎优化(SEO)也有好处。
-
返回值要求:该钩子需要返回一个 Promise,服务端会等待这个 Promise 完成后才会继续渲染组件。
五、父子组件生命周期执行顺序
(一)加载渲染过程
父组件
setup-> 父组件onBeforeMount-> 子组件setup-> 子组件onBeforeMount-> 子组件onMounted-> 父组件onMounted
父组件的 setup
在父子组件加载渲染时,首先会执行父组件的 setup(在 setup 里逻辑开始执行),接着执行父组件的 onBeforeMount,然后进入子组件的加载渲染流程。子组件会先执行 setup,接着依次执行 onBeforeMount、onMounted。最后,父组件才会执行 onMounted。
这种顺序的原因在于,父组件在创建和挂载的过程中,需要先确定自身的基本状态,然后子组件才能基于父组件传递的一些信息(如 props 等)进行自身的创建和挂载。
在实际应用场景中,比如我们在父组件 setup 里进行数据初始化,然后传递给子组件,子组件可以在自己合适的生命周期钩子中接收并处理这些数据。
(二)更新过程
父组件
onBeforeUpdate-> 子组件onBeforeUpdate-> 子组件onUpdated-> 父组件onUpdated
当父组件或子组件的数据发生变化时,生命周期钩子函数的执行顺序为:如果是父组件数据变化,会先触发父组件的 onBeforeUpdate,然后是子组件的 onBeforeUpdate,接着子组件 onUpdated,最后父组件 onUpdated。
在实际开发中,我们需要根据这个顺序来协调父子组件的操作。
- 例如,当父组件传递给子组件的 props 数据发生变化时,我们可能需要在子组件的
onBeforeUpdate钩子函数中对即将更新的数据进行一些预处理,以保证组件的正常渲染和功能实现。
(三)卸载过程
父组件
onBeforeUnmount-> 子组件onBeforeUnmount-> 子组件onUnmounted-> 父组件onUnmounted
在卸载阶段,父子组件都需要进行资源清理。尤其是当父子组件中存在一些相互关联的资源(如共同绑定的事件等)时,要按照这个顺序确保资源被正确清理,避免出现内存泄漏等问题。
六、常见问题
在哪个生命周期调用异步请求?
在 Vue 3 中,调用异步请求(如接口数据获取)通常推荐使用 onMounted 或 onServerPrefetch 生命周期钩子。
onMounted:适合客户端渲染场景,组件挂载到 DOM 后触发,常用于获取初始数据;onServerPrefetch:仅在服务端渲染(SSR)时生效,在组件即将被渲染到客户端前执行,可优化首屏加载性能。
keep-alive 缓存组件时,生命周期如何变化?
-
首次加载:
onBeforeCreate→setup()→onBeforeMount→onMounted→onActivated -
离开并缓存:触发
onDeactivated,不触发的钩子:onBeforeUnmount、onUnmounted -
重新激活:触发
onActivated,组件状态保持缓存时的值。 -
销毁(父组件卸载或缓存被清除):
onDeactivated→onBeforeUnmount→onUnmounted
Vue 2 与 Vue 3 生命周期钩子的主要区别是什么?
| Vue 2 生命周期钩子 | Vue 3 对应钩子 | 变化类型 | 使用方式 | 备注 |
|---|---|---|---|---|
beforeCreate | beforeCreate | 保留 | Options API 中直接定义 | Composition API 中不再需要,setup() 替代了 beforeCreate 和 created 的逻辑。 |
created | created | 保留 | Options API 中直接定义 | 在 Composition API 中,逻辑应写在 setup() 函数中。 |
beforeMount | onBeforeMount | 重命名 | Composition API 中通过 import { onBeforeMount } from 'vue' 并在 setup() 调用 | 功能相同,但以函数形式调用。 |
mounted | onMounted | 重命名 | 同上 | 同上 |
beforeUpdate | onBeforeUpdate | 重命名 | 同上 | 同上 |
updated | onUpdated | 重命名 | 同上 | 同上 |
beforeDestroy | onBeforeUnmount | 重命名 | 同上 | 语义更清晰,强调“卸载”而非“销毁”。 |
destroyed | onUnmounted | 重命名 | 同上 | 同上 |
errorCaptured | onErrorCaptured | 重命名 | 同上 | 功能相同,捕获子孙组件错误。 |
activated(keep-alive 专用) | onActivated | 重命名 | 同上 | 用于 keep-alive 缓存的组件激活时。 |
deactivated(keep-alive 专用) | onDeactivated | 重命名 | 同上 | 用于 keep-alive 缓存的组件失活时。 |
| - | onServerPrefetch | 新增 | Composition API 中通过 import { onServerPrefetch } from 'vue' 调用 | 服务端渲染(SSR)专用,用于在服务端预取数据。 |
| - | onRenderTracked(调试专用) | 新增 | 同上 | 仅在开发模式有效,追踪响应式依赖的收集。 |
| - | onRenderTriggered(调试专用) | 新增 | 同上 |