[Medium] 📄 Vue 生命周期钩子
1. Please explain Vue lifecycle hooks (include Vue 2 & Vue 3)
请解释 Vue 的生命周期钩子(包含 Vue 2 和 Vue 3)
Vue 组件从创建到销毁会经历一系列的过程,在这些过程中会自动调用特定的函数,这些函数就是「生命周期钩子」。理解生命周期对于掌握组件的行为非常重要。
Vue 生命周期图解
创建阶段 → 挂载阶段 → 更新阶段 → 销毁阶段
↓ ↓ ↓ ↓
Created Mounted Updated Unmounted
Vue 2 vs Vue 3 生命周期对照表
| Vue 2 (Options API) | Vue 3 (Options API) | Vue 3 (Composition API) | 说明 |
|---|---|---|---|
beforeCreate | beforeCreate | setup() | 组件实例初始化之前 |
created | created | setup() | 组件实例创建完成 |
beforeMount | beforeMount | onBeforeMount | 挂载到 DOM 之前 |
mounted | mounted | onMounted | 挂载到 DOM 之后 |
beforeUpdate | beforeUpdate | onBeforeUpdate | 数据更新前 |
updated | updated | onUpdated | 数据更新后 |
beforeDestroy | beforeUnmount | onBeforeUnmount | 组件卸载之前 |
destroyed | unmounted | onUnmounted | 组件卸载之后 |
activated | activated | onActivated | keep-alive 组件激活时 |
deactivated | deactivated | onDeactivated | keep-alive 组件停用时 |
errorCaptured | errorCaptured | onErrorCaptured | 捕获子组件错误时 |
1. 创建阶段(Creation Phase)
beforeCreate / created
<script>
export default {
data() {
return {
message: 'Hello Vue',
};
},
beforeCreate() {
// ❌ 此时 data、methods 都还未初始化
console.log('beforeCreate');
console.log(this.message); // undefined
console.log(this.$el); // undefined
},
created() {
// ✅ 此时 data、computed、methods、watch 都已初始化
console.log('created');
console.log(this.message); // 'Hello Vue'
console.log(this.$el); // undefined(尚未挂载到 DOM)
// ✅ 适合在这里发送 API 请求
this.fetchData();
},
methods: {
async fetchData() {
const response = await fetch('/api/data');
this.data = await response.json();
},
},
};
</script>
使用时机:
beforeCreate:很少使用,通常用于插件开发created:- ✅ 发送 API 请求
- ✅ 初始化非响应式的数据
- ✅ 设置事件监听器
- ❌ 无法操作 DOM(尚未挂载)
2. 挂载阶段(Mounting Phase)
beforeMount / mounted
<template>
<div ref="myElement">
<h1>{{ title }}</h1>
<canvas ref="myCanvas"></canvas>
</div>
</template>
<script>
export default {
data() {
return {
title: 'Vue Lifecycle',
};
},
beforeMount() {
// ❌ 此时虚拟 DOM 已创建,但尚未渲染到真实 DOM
console.log('beforeMount');
console.log(this.$el); // 存在,但内容是旧的(如果有的话)
console.log(this.$refs.myElement); // undefined
},
mounted() {
// ✅ 此时组件已挂载到 DOM,可以操作 DOM 元素
console.log('mounted');
console.log(this.$el); // 真实的 DOM 元素
console.log(this.$refs.myElement); // 可以访问 ref
// ✅ 适合在这里操作 DOM
this.initCanvas();
// ✅ 适合在这里使用第三方 DOM 库
this.initChart();
},
methods: {
initCanvas() {
const canvas = this.$refs.myCanvas;
const ctx = canvas.getContext('2d');
// 绘制 canvas...
},
initChart() {
// 初始化图表库(如 Chart.js, ECharts)
new Chart(this.$refs.myCanvas, {
type: 'bar',
data: {
/* ... */
},
});
},
},
};
</script>
使用时机:
beforeMount:很少使用mounted:- ✅ 操作 DOM 元素
- ✅ 初始化第三方 DOM 库(如图表、地图)
- ✅ 设置需要 DOM 的事件监听器
- ✅ 启动计时器
- ⚠️ 注意:子组件的
mounted会在父组件的mounted之前执行
3. 更新阶段(Updating Phase)
beforeUpdate / updated
<template>
<div>
<p>计数:{{ count }}</p>
<button @click="count++">增加</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0,
};
},
beforeUpdate() {
// ✅ 数据已更新,但 DOM 尚未更新
console.log('beforeUpdate');
console.log('data count:', this.count); // 新的值
console.log('DOM count:', this.$el.querySelector('p').textContent); // 旧的值
// 可以在这里访问更新前的 DOM 状态
},
updated() {
// ✅ 数据和 DOM 都已更新
console.log('updated');
console.log('data count:', this.count); // 新的值
console.log('DOM count:', this.$el.querySelector('p').textContent); // 新的值
// ⚠️ 注意:不要在这里修改数据,会导致无限循环
// this.count++; // ❌ 错误!会导致无限更新
},
};
</script>
使用时机:
beforeUpdate:需要在 DOM 更新前访问旧的 DOM 状态updated:- ✅ DOM 更新后需要执行的操作(如重新计算元素尺寸)
- ❌ 不要在这里修改数据,会导致无限更新循环
- ⚠️ 如果需要在数据变化后执行操作,建议使用
watch或nextTick
4. 销毁阶段(Unmounting Phase)
beforeUnmount / unmounted (Vue 3) / beforeDestroy / destroyed (Vue 2)
<script>
export default {
data() {
return {
timer: null,
ws: null,
};
},
mounted() {
// 设置计时器
this.timer = setInterval(() => {
console.log('计时器执行中...');
}, 1000);
// 创建 WebSocket 连接
this.ws = new WebSocket('ws://example.com');
this.ws.onmessage = (event) => {
console.log('收到消息:', event.data);
};
// 设置事件监听器
window.addEventListener('resize', this.handleResize);
document.addEventListener('click', this.handleClick);
},
beforeUnmount() {
// Vue 3 使用 beforeUnmount
// Vue 2 使用 beforeDestroy
console.log('beforeUnmount');
// 组件即将被销毁,但还可以访问数据和 DOM
},
unmounted() {
// Vue 3 使用 unmounted
// Vue 2 使用 destroyed
console.log('unmounted');
// ✅ 清理计时器
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
// ✅ 关闭 WebSocket 连接
if (this.ws) {
this.ws.close();
this.ws = null;
}
// ✅ 移除事件监听器
window.removeEventListener('resize', this.handleResize);
document.removeEventListener('click', this.handleClick);
},
methods: {
handleResize() {
console.log('窗口大小改变');
},
handleClick() {
console.log('点击事件');
},
},
};
</script>
使用时机:
beforeUnmount/beforeDestroy:很少使用unmounted/destroyed:- ✅ 清理计时器(
setInterval、setTimeout) - ✅ 移除事件监听器
- ✅ 关闭 WebSocket 连接
- ✅ 取消未完成的 API 请求
- ✅ 清理第三方库实例
- ✅ 清理计时器(
5. 特殊组件:KeepAlive
什么是 <KeepAlive>?
<KeepAlive> 是一个 Vue 的内置组件,主要功能是缓存组件实例,避免组件在切换时被销毁。
- 默认行为:当组件切换(例如路由切换或
v-if切换)时,Vue 会销毁旧组件并创建新组件。 - KeepAlive 行为:被
<KeepAlive>包裹的组件在切换时,状态会被保留在内存中,不会被销毁。
核心功能与特性
- 状态缓存:保留表单输入内容、滚动位置等。
- 性能优化:避免重复渲染和重复的 API 请求。
- 专属生命周期:提供
activated和deactivated两个独有的钩子。
适用场景
- 多标签页切换:例如后台管理系统的 Tabs。
- 列表与详情页切换:从列表页进入详情页后返回,希望能保留列表的 滚动位置和筛选条件。
- 复杂表单:填写到一半切换到其他页面查看数据,返回时表单内容不应丢失。
使用示例
<template>
<KeepAlive include="UserList,ProductList">
<component :is="currentComponent" />
</KeepAlive>
</template>
include:只有名称匹配的组件会被缓存。exclude:名称匹配的组件不会被缓存。max:最多缓存多少个组件实例。