跳到主要内容

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 APIComposition API说明
beforeCreatesetup() 开始实例初始化
createdsetup() 结束数据已初始化
beforeMountonBeforeMount挂载前
mountedonMounted挂载完成
beforeUpdateonBeforeUpdate更新前
updatedonUpdated更新完成
beforeUnmountonBeforeUnmount卸载前
unmountedonUnmounted卸载完成
activatedonActivatedkeep-alive 激活
deactivatedonDeactivatedkeep-alive 停用
errorCapturedonErrorCaptured捕获错误
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 有什么区别?应该在哪里发请求?

答案

特性createdmounted
数据访问
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 2Vue 3说明
beforeCreatesetup()合并到 setup
createdsetup()合并到 setup
beforeDestroybeforeUnmount重命名
destroyedunmounted重命名
-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 中保存引用)
});
}
};
忘记清理的后果
  • 内存泄漏:事件监听、定时器持续存在
  • 重复执行:组件多次挂载/卸载累积副作用
  • 状态错误:异步操作更新已卸载组件的状态

相关链接