Vue 生命周期
问题
Vue 组件的生命周期有哪些阶段?Options API 和 Composition API 的生命周期钩子有什么区别?
答案
Vue 组件从创建到销毁会经历一系列生命周期阶段,在每个阶段可以执行特定的逻辑。
生命周期流程图
Options API 生命周期
export default {
// 1. 创建阶段
beforeCreate() {
// 实例初始化之后,数据观测和事件配置之前
// this 存在,但 data/methods 还没初始化
console.log(this.message); // undefined
},
created() {
// 实例创建完成,data/methods 已初始化
// 可以访问数据、发起请求,但 DOM 还没生成
console.log(this.message); // ✅ 可以访问
this.fetchData(); // ✅ 可以发起请求
},
// 2. 挂载阶段
beforeMount() {
// 模板编译完成,即将挂载到 DOM
// $el 还不可用
},
mounted() {
// DOM 已挂载,可以操作 DOM、初始化第三方库
console.log(this.$el); // ✅ 可以访问 DOM
this.$refs.chart.binBottom // ✅ 可以访问 refs
},
// 3. 更新阶段
beforeUpdate() {
// 数据变化,DOM 更新之前
// 可以在这里访问更新前的 DOM
},
updated() {
// DOM 已更新
// 避免在这里修改数据,可能导致循环更新
},
// 4. 卸载阶段
beforeUnmount() {
// Vue 3(Vue 2 是 beforeDestroy)
// 实例仍然完全可用,清理定时器、事件监听
clearInterval(this.timer);
window.removeEventListener('resize', this.handleResize);
},
unmounted() {
// Vue 3(Vue 2 是 destroyed)
// 实例已卸载,所有子组件也已卸载
},
// 5. keep-alive 专用
activated() {
// 被 keep-alive 缓存的组件激活时
},
deactivated() {
// 被 keep-alive 缓存的组件停用时
},
// 6. 错误处理
errorCaptured(err, instance, info) {
// 捕获来自后代组件的错误
console.error('Error:', err);
return false; // 阻止错误继续传播
}
};
Composition API 生命周期
Composition API 提供 onXxx 形式的生命周期钩子:
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onActivated,
onDeactivated,
onErrorCaptured
} from 'vue';
export default {
setup() {
// setup 本身替代 beforeCreate 和 created
// 这里的代码相当于在 created 中执行
onBeforeMount(() => {
console.log('即将挂载');
});
onMounted(() => {
console.log('已挂载,可以操作 DOM');
});
onBeforeUpdate(() => {
console.log('即将更新');
});
onUpdated(() => {
console.log('已更新');
});
onBeforeUnmount(() => {
console.log('即将卸载');
});
onUnmounted(() => {
console.log('已卸载');
});
// keep-alive
onActivated(() => {
console.log('组件激活');
});
onDeactivated(() => {
console.log('组件停用');
});
// 错误处理
onErrorCaptured((err, instance, info) => {
console.error('捕获错误:', err);
return false;
});
}
};
生命周期对照表
| Options API | Composition API | 说明 |
|---|---|---|
beforeCreate | setup() 开始 | 实例初始化 |
created | setup() 结束 | 数据已初始化 |
beforeMount | onBeforeMount | 挂载前 |
mounted | onMounted | 挂载完成 |
beforeUpdate | onBeforeUpdate | 更新前 |
updated | onUpdated | 更新完成 |
beforeUnmount | onBeforeUnmount | 卸载前 |
unmounted | onUnmounted | 卸载完成 |
activated | onActivated | keep-alive 激活 |
deactivated | onDeactivated | keep-alive 停用 |
errorCaptured | onErrorCaptured | 捕获错误 |
setup 替代 beforeCreate 和 created
// Options API
export default {
beforeCreate() {
console.log('beforeCreate');
},
created() {
console.log('created');
this.fetchData();
}
};
// Composition API
export default {
setup() {
// setup 中的代码执行时机等同于 beforeCreate 和 created 之间
console.log('setup 执行');
fetchData();
}
};
父子组件生命周期顺序
// 挂载顺序
// Parent setup → Parent onBeforeMount → Child setup → Child onBeforeMount
// → Child onMounted → Parent onMounted
// 更新顺序
// Parent onBeforeUpdate → Child onBeforeUpdate → Child onUpdated → Parent onUpdated
// 卸载顺序
// Parent onBeforeUnmount → Child onBeforeUnmount → Child onUnmounted → Parent onUnmounted
常见使用场景
import { ref, onMounted, onUnmounted, watch } from 'vue';
export function useMousePosition() {
const x = ref(0);
const y = ref(0);
function update(e: MouseEvent) {
x.value = e.pageX;
y.value = e.pageY;
}
// 挂载时添加事件监听
onMounted(() => {
window.addEventListener('mousemove', update);
});
// 卸载时移除事件监听
onUnmounted(() => {
window.removeEventListener('mousemove', update);
});
return { x, y };
}
// 组件中的典型用法
import { ref, onMounted, onUnmounted } from 'vue';
export default {
setup() {
const data = ref(null);
const timer = ref<number>();
// 1. onMounted:获取数据、初始化第三方库
onMounted(async () => {
data.value = await fetchData();
// 初始化图表
const chart = echarts.init(document.getElementById('chart'));
chart.setOption({ /* ... */ });
});
// 2. onMounted:定时任务
onMounted(() => {
timer.value = window.setInterval(() => {
console.log('tick');
}, 1000);
});
// 3. onUnmounted:清理资源
onUnmounted(() => {
if (timer.value) {
clearInterval(timer.value);
}
});
return { data };
}
};
script setup 中使用生命周期
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
const count = ref(0);
// 直接使用,无需 setup 函数
onMounted(() => {
console.log('组件已挂载');
});
onUnmounted(() => {
console.log('组件已卸载');
});
</script>
常见面试问题
Q1: 在哪个生命周期可以访问 DOM?
答案:
mounted(或 onMounted) 是第一个可以访问 DOM 的生命周期。
// ❌ beforeMount 中访问 DOM
onBeforeMount(() => {
console.log(document.querySelector('#app')); // null 或上一次的 DOM
});
// ✅ mounted 中访问 DOM
onMounted(() => {
console.log(document.querySelector('#app')); // ✅ 可以访问
const el = templateRef.value; // ✅ ref 可用
});
但要注意:mounted 不保证所有子组件都已挂载。如果需要等待整个视图渲染完成:
import { onMounted, nextTick } from 'vue';
onMounted(async () => {
await nextTick();
// 此时所有子组件都已渲染完成
});
Q2: created 和 mounted 有什么区别?应该在哪里发请求?
答案:
| 特性 | created | mounted |
|---|---|---|
| 数据访问 | ✅ | ✅ |
| DOM 访问 | ❌ | ✅ |
| 执行时机 | 更早 | 更晚 |
| SSR 中执行 | ✅ | ❌ |
请求数据:两者都可以,但有区别:
// 推荐:setup 或 created 中发请求
setup() {
const data = ref(null);
// 立即发起请求,不等待 DOM
fetchData().then(res => {
data.value = res;
});
return { data };
}
// 需要 DOM 信息时才用 mounted
onMounted(() => {
const width = container.value.offsetWidth;
fetchDataWithWidth(width);
});
SSR 场景:created 在服务端也会执行,mounted 只在客户端执行。
Q3: 父子组件的生命周期执行顺序是什么?
答案:
挂载阶段:父组件先创建,子组件先挂载
父 setup → 父 onBeforeMount → 子 setup → 子 onBeforeMount
→ 子 onMounted → 父 onMounted
更新阶段:父组件先进入更新,子组件先完成更新
父 onBeforeUpdate → 子 onBeforeUpdate → 子 onUpdated → 父 onUpdated
卸载阶段:父组件先开始卸载,子组件先完成卸载
父 onBeforeUnmount → 子 onBeforeUnmount → 子 onUnmounted → 父 onUnmounted
Q4: Vue 3 相比 Vue 2 生命周期有什么变化?
答案:
| Vue 2 | Vue 3 | 说明 |
|---|---|---|
beforeCreate | setup() | 合并到 setup |
created | setup() | 合并到 setup |
beforeDestroy | beforeUnmount | 重命名 |
destroyed | unmounted | 重命名 |
| - | onRenderTracked | 新增:调试用 |
| - | onRenderTriggered | 新增:调试用 |
// Vue 3 调试钩子
import { onRenderTracked, onRenderTriggered } from 'vue';
onRenderTracked((e) => {
// 追踪组件渲染时访问的响应式依赖
console.log('tracked:', e.target, e.key);
});
onRenderTriggered((e) => {
// 响应式依赖变化触发重新渲染
console.log('triggered:', e.target, e.key, e.oldValue, e.newValue);
});
Q5: 如何在组件卸载时正确清理资源?
答案:
import { ref, onMounted, onUnmounted } from 'vue';
export default {
setup() {
const timer = ref<number>();
const controller = new AbortController();
onMounted(() => {
// 1. 定时器
timer.value = setInterval(() => {
console.log('tick');
}, 1000);
// 2. 事件监听
window.addEventListener('resize', handleResize);
// 3. WebSocket
const ws = new WebSocket('ws://example.com');
ws.onmessage = handleMessage;
});
onUnmounted(() => {
// 清理定时器
if (timer.value) clearInterval(timer.value);
// 移除事件监听
window.removeEventListener('resize', handleResize);
// 取消请求
controller.abort();
// 关闭 WebSocket(在 setup 中保存引用)
});
}
};
忘记清理的后果
- 内存泄漏:事件监听、定时器持续存在
- 重复执行:组件多次挂载/卸载累积副作用
- 状态错误:异步操作更新已卸载组件的状态