一、引言
在 Vue.js 的组件化开发中,组件的生命周期是理解其运行机制的核心。每个 Vue 组件从创建到销毁都会经历一系列 有序的阶段,每个阶段都提供了特定的 生命周期钩子(Lifecycle Hooks),允许开发者在组件的不同状态下执行自定义逻辑(如初始化数据、操作 DOM、清理定时器等)。
无论是初学者还是资深开发者,掌握生命周期钩子的使用场景与执行顺序,都是编写高效、可维护 Vue 应用的必备技能。本文将围绕 Vue 组件的 完整生命周期(创建→挂载→更新→销毁),从技术背景、应用场景、代码实现、原理解释到实战演示,全方位解析其原理与最佳实践,帮助开发者深入理解组件的“生命旅程”。
二、技术背景
1. 什么是生命周期?
Vue 组件的生命周期指的是 组件从被创建到挂载到 DOM,再到数据更新、最终被销毁的整个过程。在这个过程中,Vue 会在特定的时间点触发一系列 生命周期钩子函数,开发者可以通过这些钩子函数在组件的不同阶段插入自定义逻辑。
2. 生命周期的核心阶段
Vue 组件的生命周期可以分为 四个主要阶段,每个阶段包含多个具体的钩子函数:
|
|
主要钩子函数(Vue 3 Composition API)
|
主要钩子函数(Vue 2 Options API)
|
|
组件实例被创建,初始化数据、计算属性、方法等,但尚未挂载到 DOM。
|
setup() (核心入口)、onBeforeCreate 、onCreated
|
|
|
组件被挂载到真实的 DOM 中,此时可以操作 DOM 元素。
|
|
|
|
组件的数据发生变化,触发重新渲染,最终更新 DOM。
|
|
|
|
组件从 DOM 中移除,释放资源(如清除定时器、取消事件监听)。
|
onBeforeUnmount 、onUnmounted
|
|
注意:Vue 3 推荐使用 Composition API(setup()
函数 + onXxx
钩子),但依然兼容 Options API(data
、mounted
等选项);Vue 2 仅支持 Options API。
三、应用使用场景
1. 创建阶段(Initialization)
-
初始化数据:在组件创建时,从后端 API 获取初始数据(如用户信息、配置项);
-
设置全局状态:注册全局事件监听器(如
window.resize
)或初始化第三方库(如图表库 ECharts);
-
验证 Props:在组件创建时检查传入的 Props 是否符合预期(如必填字段校验)。
2. 挂载阶段(DOM Insertion)
-
操作 DOM:在组件挂载到 DOM 后,获取 DOM 元素的引用(如通过
ref
获取输入框并聚焦);
-
初始化第三方插件:如初始化地图组件(高德地图、百度地图)、富文本编辑器(Quill、TinyMCE);
-
触发首次数据加载:如发送异步请求获取列表数据(需确保 DOM 已存在)。
3. 更新阶段(Re-rendering)
-
响应数据变化:当组件的响应式数据(如
data
、props
)发生变化时,执行额外的逻辑(如重新计算衍生数据);
-
优化性能:在更新前判断是否需要重新渲染(如通过
v-if
控制子组件的显示/隐藏);
-
同步外部状态:如更新后同步滚动位置、重新初始化依赖 DOM 的插件。
4. 销毁阶段(Cleanup)
-
释放资源:清除定时器(如
setInterval
)、取消网络请求(如 Axios 的 CancelToken)、移除事件监听器(如 window.scroll
);
-
销毁第三方实例:如销毁图表实例(ECharts)、关闭 WebSocket 连接;
-
避免内存泄漏:确保组件销毁时,所有与之关联的外部资源都被正确清理。
四、不同场景下详细代码实现
场景 1:Vue 3 Composition API(完整生命周期示例)
需求:封装一个用户信息卡片组件(UserInfoCard.vue
),展示用户的基本信息(姓名、年龄),并在组件的不同生命周期阶段输出日志(模拟初始化数据、操作 DOM、清理定时器)。
1.1 用户信息卡片组件(UserInfoCard.vue)
<template>
<div class="user-card">
<h3>用户信息卡片(Vue 3 Composition API)</h3>
<p><strong>姓名:</strong>{{ user.name }}</p>
<p><strong>年龄:</strong>{{ user.age }}</p>
<button @click="updateAge">年龄 +1(触发更新)</button>
</div>
</template>
<script setup>
import { ref, onBeforeCreate, onCreate, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue';
// 响应式数据
const user = ref({ name: '张三', age: 25 });
let timer = null; // 定时器引用(用于销毁阶段清理)
// --- 创建阶段 ---
onBeforeCreate(() => {
console.log('🔹 onBeforeCreate:组件实例刚被创建,data 和 methods 未初始化');
});
onCreate(() => {
console.log('🔹 onCreate:组件实例已创建,data 和 methods 已初始化(但未挂载到 DOM)');
// 模拟初始化数据(如从后端获取用户信息)
setTimeout(() => {
user.value.name = '李四'; // 修改初始数据(但此时还未挂载,DOM 不可见)
}, 500);
});
// --- 挂载阶段 ---
onBeforeMount(() => {
console.log('🔹 onBeforeMount:组件即将挂载到 DOM(template 已编译,但未插入真实 DOM)');
});
onMounted(() => {
console.log('🔹 onMounted:组件已挂载到 DOM(可以操作 DOM 元素或初始化第三方插件)');
// 操作 DOM:获取输入框焦点(示例)
console.log('✅ DOM 已挂载,当前用户年龄:', user.value.age);
// 模拟启动定时器(如轮询数据)
timer = setInterval(() => {
console.log('🔄 定时器运行中... 当前年龄:', user.value.age);
}, 2000);
});
// --- 更新阶段 ---
onBeforeUpdate(() => {
console.log('🔹 onBeforeUpdate:组件数据即将更新(DOM 未重新渲染)');
});
onUpdated(() => {
console.log('🔹 onUpdated:组件数据已更新,DOM 已重新渲染');
});
// --- 销毁阶段 ---
onBeforeUnmount(() => {
console.log('🔹 onBeforeUnmount:组件即将被销毁(清理定时器、事件监听器等)');
if (timer) {
clearInterval(timer); // 清理定时器(避免内存泄漏)
timer = null;
}
});
onUnmounted(() => {
console.log('🔹 onUnmounted:组件已销毁(从 DOM 中移除)');
});
// 方法:更新用户年龄(触发更新阶段)
const updateAge = () => {
user.value.age += 1;
};
</script>
<style scoped>
.user-card {
padding: 16px;
border: 1px solid #eee;
border-radius: 8px;
margin: 10px;
}
button {
margin-top: 10px;
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
1.2 父组件(App.vue)
<template>
<div id="app">
<h1>Vue 3 组件生命周期钩子示例</h1>
<UserInfoCard />
<button @click="destroyComponent">销毁组件(模拟卸载)</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
import UserInfoCard from './components/UserInfoCard.vue';
const showComponent = ref(true);
const destroyComponent = () => {
showComponent.value = false; // 通过 v-if 控制组件销毁(实际项目中可能是路由切换等场景)
};
// 注意:这里简化了销毁逻辑,实际 UserInfoCard 通过 v-if 控制显示/隐藏
// 为了演示销毁钩子,需将 UserInfoCard 包裹在 v-if 中(代码略,可自行扩展)
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
padding: 20px;
max-width: 500px;
margin: 0 auto;
}
</style>
-
打开浏览器控制台(F12),观察组件在不同阶段输出的日志(如
onBeforeCreate
、onMounted
、onUpdated
);
-
点击“年龄 +1”按钮,触发更新阶段的钩子(
onBeforeUpdate
→ onUpdated
);
-
点击“销毁组件”按钮(需扩展代码,如通过
v-if
控制 UserInfoCard
的显示),观察销毁阶段的钩子(onBeforeUnmount
→ onUnmounted
)。
场景 2:Vue 2 Options API(经典生命周期示例)
需求:封装一个计数器组件(Counter.vue
),通过 Options API 的生命周期钩子(如 created
、mounted
、updated
)实现数据初始化、DOM 操作和更新逻辑。
2.1 计数器组件(Counter.vue)
<template>
<div class="counter">
<h3>计数器(Vue 2 Options API)</h3>
<p>当前计数:{{ count }}</p>
<button @click="increment">+1(触发更新)</button>
</div>
</template>
<script>
export default {
name: 'Counter',
data() {
return {
count: 0,
};
},
beforeCreate() {
console.log('🔹 beforeCreate:组件实例刚被创建,data 和 methods 未初始化');
},
created() {
console.log('🔹 created:组件实例已创建,data 和 methods 已初始化(但未挂载到 DOM)');
// 初始化数据(如从后端获取初始计数)
this.count = parseInt(localStorage.getItem('counter') || '0', 10);
},
beforeMount() {
console.log('🔹 beforeMount:组件即将挂载到 DOM');
},
mounted() {
console.log('🔹 mounted:组件已挂载到 DOM(可以操作 DOM 或初始化插件)');
// 操作 DOM:获取按钮引用(示例)
console.log('✅ DOM 已挂载,当前计数:', this.count);
// 模拟从本地存储加载数据
if (localStorage.getItem('counter')) {
console.log('📦 已加载本地存储的计数:', this.count);
}
},
beforeUpdate() {
console.log('🔹 beforeUpdate:组件数据即将更新(DOM 未重新渲染)');
},
updated() {
console.log('🔹 updated:组件数据已更新,DOM 已重新渲染');
// 更新后同步到本地存储
localStorage.setItem('counter', this.count.toString());
},
beforeDestroy() {
console.log('🔹 beforeDestroy:组件即将被销毁(清理资源)');
// 清理逻辑(如移除事件监听器)
},
destroyed() {
console.log('🔹 destroyed:组件已销毁(从 DOM 中移除)');
},
methods: {
increment() {
this.count += 1;
},
},
};
</script>
<style scoped>
.counter {
padding: 16px;
border: 1px solid #eee;
border-radius: 8px;
margin: 10px;
}
button {
margin-top: 10px;
padding: 8px 16px;
background: #28a745;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
2.2 父组件(App.vue)
<template>
<div id="app">
<h1>Vue 2 组件生命周期钩子示例</h1>
<Counter />
</div>
</template>
<script>
import Counter from './components/Counter.vue';
export default {
name: 'App',
components: { Counter },
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
padding: 20px;
max-width: 500px;
margin: 0 auto;
}
</style>
-
打开控制台,观察计数器组件在不同阶段的日志(如
created
中初始化数据、mounted
中操作 DOM、updated
中同步数据到本地存储);
-
点击“+1”按钮,触发更新阶段的钩子(
beforeUpdate
→ updated
),并看到计数持久化到本地存储。
五、原理解释
1. 生命周期的核心机制
Vue 组件的生命周期是由 Vue 内部引擎控制的 有序流程,每个阶段对应特定的钩子函数,开发者可以通过这些钩子在组件的不同状态下执行逻辑。其核心原理如下:
-
-
Vue 首先调用
beforeCreate
,此时组件实例刚被创建,但 data
、methods
等选项尚未初始化;
-
接着执行
created
,此时组件的响应式数据(data
)、计算属性(computed
)、方法(methods
)已初始化完成,但组件还未挂载到 DOM。
-
-
Vue 调用
beforeMount
,此时组件的模板已编译成渲染函数,但尚未将生成的虚拟 DOM 插入到真实 DOM 中;
-
随后执行
mounted
,此时组件已挂载到 DOM(可通过 this.$el
访问根 DOM 元素),可以安全地操作 DOM 或初始化依赖 DOM 的第三方库。
-
-
当组件的响应式数据(如
data
、props
)发生变化时,Vue 会先调用 beforeUpdate
,此时新的虚拟 DOM 已生成,但还未替换旧的 DOM;
-
接着重新渲染组件并生成新的虚拟 DOM,最后调用
updated
,此时 DOM 已更新为最新状态。
-
-
当组件被销毁(如通过
v-if
控制为 false
或路由切换)时,Vue 先调用 beforeUnmount
(Vue 3)/ beforeDestroy
(Vue 2),此时可以清理定时器、取消事件监听器、销毁第三方实例;
-
最后调用
unmounted
(Vue 3)/ destroyed
(Vue 2),组件已从 DOM 中移除,所有关联的资源应已被清理。
2. Vue 3 与 Vue 2 的钩子差异
|
|
|
|
通过 onBeforeCreate 、onMounted 等函数定义
|
通过 beforeCreate 、mounted 等选项定义
|
|
setup() 函数(在 beforeCreate 之前执行)
|
|
|
使用 ref 、reactive 等组合式 API 管理
|
|
|
|
逻辑分散在各个选项(如 data 、methods )
|
注意:Vue 3 的 setup()
函数在 beforeCreate
之前执行,因此在 setup
中无法访问 this
(组件实例未创建),但可以直接使用 ref
和 reactive
定义响应式数据。
六、核心特性
|
|
|
生命周期钩子按照固定的顺序触发(创建→挂载→更新→销毁),不可颠倒;
|
|
每个阶段(创建、挂载、更新、销毁)包含多个钩子,覆盖组件的全生命周期;
|
|
开发者可根据需求选择在特定阶段执行逻辑(如只在挂载后操作 DOM);
|
|
更新阶段的钩子与 Vue 的响应式系统深度集成,自动响应数据变化;
|
|
销毁阶段的钩子用于清理定时器、事件监听器等,避免内存泄漏;
|
|
Composition API 的 setup() 函数提供了更灵活的逻辑组织和复用能力;
|
七、原理流程图及原理解释
原理流程图(Vue 3 组件生命周期)
+-----------------------+
| 组件创建 |
| (beforeCreate) | --> 组件实例刚创建,data/methods 未初始化
+-----------------------+
|
v
+-----------------------+
| 组件创建完成 |
| (created) | --> data/methods 已初始化,但未挂载到 DOM
+-----------------------+
|
v
+-----------------------+
| 即将挂载 DOM |
| (beforeMount) | --> 模板已编译,但未插入真实 DOM
+-----------------------+
|
v
+-----------------------+
| 已挂载到 DOM |
| (mounted) | --> 组件已渲染到页面,可操作 DOM
+-----------------------+
|
v
+-----------------------+ <-- 数据变化触发更新
| 即将更新 DOM |
| (beforeUpdate) | --> 新的虚拟 DOM 已生成,但未替换旧 DOM
+-----------------------+
|
v
+-----------------------+
| 已更新 DOM |
| (updated) | --> DOM 已同步为最新状态
+-----------------------+
|
v
+-----------------------+ <-- 组件被销毁(如 v-if=false)
| 即将销毁 |
| (beforeUnmount) | --> 清理定时器、事件监听器
+-----------------------+
|
v
+-----------------------+
| 已销毁 |
| (unmounted) | --> 组件从 DOM 中移除
+-----------------------+
原理解释
-
创建阶段:Vue 首先执行
beforeCreate
(此时组件实例刚生成,但 data
和 methods
未初始化),随后执行 created
(此时所有响应式数据和方法已就绪,但组件还未挂载到 DOM)。
-
挂载阶段:接着执行
beforeMount
(模板已编译成渲染函数,但虚拟 DOM 未插入真实 DOM),最后执行 mounted
(组件已挂载到页面,可通过 this.$el
或 ref
访问 DOM 元素)。
-
更新阶段:当响应式数据(如用户点击按钮修改
count
)变化时,Vue 重新计算虚拟 DOM,先执行 beforeUpdate
(新的虚拟 DOM 已生成),然后更新 DOM 并执行 updated
(DOM 已同步为最新状态)。
-
销毁阶段:当组件被移除(如路由切换或
v-if
变为 false
),先执行 beforeUnmount
(清理定时器、取消事件监听),最后执行 unmounted
(组件完全从 DOM 中移除,所有资源已释放)。
八、环境准备
1. 开发环境
-
Vue 2 或 Vue 3:生命周期钩子在两个版本中均支持,但 Vue 3 推荐使用 Composition API(
setup()
+ onXxx
);
-
开发工具:Vue CLI 或 Vite(用于快速创建项目);
-
浏览器:Chrome/Firefox(用于查看控制台日志)。
2. 项目配置
-
确保项目的 Vue 版本正确(通过
package.json
检查 vue
依赖版本);
-
若使用 Vue 3 的 Composition API,需熟悉
<script setup>
语法或 setup()
函数的定义方式。
九、实际详细应用代码示例实现
完整项目代码(整合上述场景)
1. Vue 3 组件(UserInfoCard.vue)
2. Vue 2 组件(Counter.vue)
3. 主应用(App.vue)
<template>
<div id="app">
<h1>Vue 组件生命周期钩子完整示例</h1>
<!-- Vue 3 组件示例 -->
<section>
<h2>Vue 3:用户信息卡片(Composition API)</h2>
<UserInfoCard />
</section>
<!-- Vue 2 组件示例 -->
<section>
<h2>Vue 2:计数器(Options API)</h2>
<Counter />
</section>
</div>
</template>
<script setup>
import UserInfoCard from './components/UserInfoCard.vue';
import Counter from './components/Counter.vue';
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
section {
margin-bottom: 40px;
border: 1px solid #eee;
padding: 20px;
border-radius: 8px;
}
</style>
-
打开控制台,分别观察 Vue 3 组件(UserInfoCard)和 Vue 2 组件(Counter)在不同生命周期阶段输出的日志;
-
通过交互(如点击按钮更新数据)验证更新阶段的钩子触发逻辑。
十、运行结果
1. Vue 3 组件的表现
-
控制台输出创建阶段(
beforeCreate
→ created
)、挂载阶段(beforeMount
→ mounted
)、更新阶段(beforeUpdate
→ updated
)、销毁阶段(beforeUnmount
→ unmounted
)的日志;
-
点击“年龄 +1”按钮,触发更新阶段的钩子,同时定时器每 2 秒输出当前年龄(模拟数据变化)。
2. Vue 2 组件的表现
-
控制台输出经典生命周期钩子的日志(如
beforeCreate
→ created
→ mounted
),点击“+1”按钮触发 beforeUpdate
→ updated
,并同步数据到本地存储。
十一、测试步骤以及详细代码
1. 测试目标
-
组件在不同阶段(创建、挂载、更新、销毁)是否正确触发对应的钩子;
-
-
2. 测试步骤
步骤 1:启动项目
npm run serve # Vue 2
# 或 npm run dev # Vue 3 (Vite)
访问 http://localhost:5173
(Vite 默认端口),查看 Vue 3 和 Vue 2 组件的示例。
步骤 2:测试创建与挂载阶段
-
打开浏览器控制台,观察组件初始化时输出的日志(如 Vue 3 的
beforeCreate
→ created
→ beforeMount
→ mounted
);
-
确认组件是否在挂载后正确显示(如用户信息卡片、计数器初始值)。
步骤 3:测试更新阶段
-
点击 Vue 3 组件的“年龄 +1”按钮或 Vue 2 组件的“+1”按钮,观察控制台输出的
beforeUpdate
→ updated
日志;
-
确认组件数据和 DOM 是否同步更新(如年龄数字增加、计数器值变化)。
步骤 4:测试销毁阶段
-
对于 Vue 3 组件,点击“销毁组件”按钮(需扩展代码,如通过
v-if
控制显示),观察 beforeUnmount
→ unmounted
日志;
-
对于 Vue 2 组件,通过路由切换或其他方式销毁组件,确认定时器是否被清理(避免内存泄漏)。
十二、部署场景
1. 生产环境注意事项
-
资源清理:确保在销毁阶段(
beforeUnmount
/destroyed
)清理所有定时器、事件监听器和第三方实例,避免内存泄漏;
-
性能优化:在更新阶段(
beforeUpdate
)可通过逻辑判断减少不必要的重新渲染(如使用 v-if
控制子组件的显示);
-
错误监控:在创建阶段(
created
)或挂载阶段(mounted
)添加错误边界逻辑(如捕获异步请求的异常)。
2. 适用场景
-
初始化逻辑:如从后端加载初始数据、注册全局事件监听器;
-
DOM 操作:如组件挂载后获取元素引用、初始化图表库;
-
数据同步:如更新后同步状态到本地存储或后端;
-
资源管理:如销毁时清理定时器、关闭 WebSocket 连接。
十三、疑难解答
1. 问题 1:生命周期钩子未触发?
-
Vue 3 中使用了错误的钩子函数(如拼写错误,应为
onMounted
而非 mounted
);
-
组件未被正确挂载(如通过
v-if="false"
永久隐藏,导致 mounted
不触发);
-
Vue 2 中 Options API 的钩子选项拼写错误(如
beforeMount
写成 `
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
评论(0)