🔄 Vue 3 中 Keep-Alive 对生命周期的影响:深度解析
在 Vue 开发中,性能优化是一个永恒的话题。当我们使用 <keep-alive> 包裹组件时,组件会被缓存而不是销毁。这虽然提升了性能,但也改变了组件的生命周期行为。
很多开发者会遇到这样的困惑:
"为什么我切换标签页后,
onMounted不再执行了?""为什么我在
onUnmounted里清理定时器,结果定时器还在跑?"
本文将带你揭开 keep-alive 的神秘面纱,掌握新增的两个关键生命周期钩子:activated 和 deactivated。
📂 目录
- [核心概念:什么是 Keep-Alive?](#核心概念:什么是 Keep-Alive?)
- [生命周期对比:普通组件 vs 缓存组件](#生命周期对比:普通组件 vs 缓存组件)
- [新增钩子:Activated & Deactivated](#新增钩子:Activated & Deactivated)
- 实战演示:代码与现象
- [⚠️ 常见坑点与最佳实践](#⚠️ 常见坑点与最佳实践)
- [💡 总结](#💡 总结)
1. 核心概念:什么是 Keep-Alive?
<keep-alive> 是 Vue 内置的一个抽象组件,它不会渲染成 DOM 元素,也不会出现在父组件链中。
它的作用:
- 缓存不活动的组件实例,而不是销毁它们。
- 当组件再次被切换回来时,直接从缓存中读取,跳过创建和挂载过程。
典型场景:
- 标签页切换(如:首页、列表页、详情页)。
- 表单填写中途切换,希望保留用户输入的内容。
vue
<template>
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
</template>
2. 生命周期对比:普通组件 vs 缓存组件
理解 keep-alive 的关键,在于对比首次进入 、切换离开 、再次进入这三个阶段的生命周期变化。
📊 生命周期流程图
| 阶段 | 普通组件 (无 Keep-Alive) | 缓存组件 (有 Keep-Alive) | 说明 |
|---|---|---|---|
| 首次渲染 | beforeCreate -> created -> beforeMount-> mounted |
beforeCreate -> created -> beforeMount-> mounted -> activated |
首次进入时,activated 会在 mounted 之后触发。 |
| 切换离开 | beforeUnmount -> unmounted |
deactivated |
关键点! 组件不会被销毁,而是触发 deactivated。 |
| 再次进入 | beforeCreate -> ... -> mounted (重新创建) |
activated |
关键点! 直接从缓存恢复,只触发 activated,不触发 mounted |
🔑 核心结论:
- 被
keep-alive缓存的组件,onMounted和onUnmounted只会执行一次(分别在第一次创建和最终真正销毁时)。- 后续的切换,只会触发
onActivated和onDeactivated。
3. 新增钩子:Activated & Deactivated
Vue 3 Composition API 中提供了两个新的生命周期钩子来处理缓存组件的状态:
✅ onActivated
- 触发时机:组件被从缓存中激活(显示)时。
- 用途 :
- 重新获取最新数据(如果数据需要实时性)。
- 重新启动定时器、动画或监听器。
- 滚动位置恢复。
✅ onDeactivated
- 触发时机:组件被缓存(隐藏)时。
- 用途 :
- 暂停定时器、视频播放、音频播放。
- 取消不必要的网络请求。
- 保存临时状态。
4. 实战演示:代码与现象
让我们通过一个具体的例子来观察生命周期的执行顺序。
子组件 (CachedComponent.vue)
vue
<template>
<div class="box">
<h3>当前计数: {{ count }}</h3>
<button @click="count++">增加</button>
<p>日志: {{ logs.join(" -> ") }}</p>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, onActivated, onDeactivated } from "vue";
const count = ref(0);
const logs = ref([]);
const addLog = (msg) => {
logs.value.push(msg);
console.log(msg);
};
// 1. 初始化阶段
onMounted(() => {
addLog("onMounted");
});
// 2. 激活阶段(首次进入 & 再次进入)
onActivated(() => {
addLog("onActivated");
});
// 3. 停用阶段(切换离开)
onDeactivated(() => {
addLog("onDeactivated");
});
// 4. 销毁阶段(只有当 keep-alive 排除该组件或父组件销毁时才触发)
onUnmounted(() => {
addLog("onUnmounted");
});
</script>
父组件 (App.vue)
vue
<template>
<div>
<button @click="toggle">切换组件</button>
<keep-alive>
<CachedComponent v-if="show" />
</keep-alive>
</div>
</template>
<script setup>
import { ref } from "vue";
import CachedComponent from "./CachedComponent.vue";
const show = ref(true);
const toggle = () => {
show.value = !show.value;
};
</script>
🎬 现象观察
-
首次加载:
- 控制台输出:
onMounted->onActivated - 页面显示:计数为 0。
- 控制台输出:
-
点击"切换组件"隐藏再显示:
- 控制台输出:
onDeactivated(隐藏时) ->onActivated(显示时) - 注意 :
onMounted没有再次执行! - 页面显示:计数保持之前的值(例如你之前加到了 5,现在还是 5),证明组件状态被保留了。
- 控制台输出:
-
最终销毁(如关闭标签页或移除 keep-alive):
- 控制台输出:
onDeactivated->onUnmounted
- 控制台输出:
⚠️ 常见坑点与最佳实践
❌ 坑点 1:在 onMounted 中请求数据,导致数据不更新
问题 :如果你只在 onMounted 中调用 API 获取列表数据,当用户从详情页返回列表页时,由于 onMounted 不执行,列表数据不会刷新,可能显示旧数据。
✅ 解决方案:
- 将数据请求逻辑放入
onActivated。 - 或者在
onActivated中判断是否需要刷新。
javascript
onActivated(() => {
fetchData(); // 每次激活都重新获取最新数据
});
❌ 坑点 2:定时器/监听器未在 onDeactivated 中清理
问题 :在 onMounted 中启动了 setInterval 或 window.addEventListener,但在 onUnmounted 中清理。由于组件被缓存时不会触发 onUnmounted,导致定时器在后台继续运行,造成内存泄漏或性能问题。
✅ 解决方案:
- 在
onDeactivated中暂停或清理资源。 - 在
onActivated中重新启动。
javascript
let timer = null;
onActivated(() => {
timer = setInterval(() => {
console.log("tick");
}, 1000);
});
onDeactivated(() => {
if (timer) clearInterval(timer);
});
❌ 坑点 3:误以为 keep-alive 会缓存所有状态
注意 :keep-alive 缓存的是组件实例 。如果组件内部使用了非响应式变量(如普通 let 变量),或者依赖外部全局状态但未正确订阅,可能会出现预期外的行为。务必确保状态管理的一致性。
💡 总结
| 特性 | 普通组件 | Keep-Alive 组件 |
|---|---|---|
| 创建/挂载 | 每次切换都执行 | 仅首次执行 |
| 销毁/卸载 | 每次切换都执行 | 仅最终移除时执行 |
| 激活/停用 | 无 | onActivated / onDeactivated |
| 状态保留 | 不保留 | 保留 (包括 data, computed, DOM 状态) |
| 适用场景 | 一次性页面、简单组件 | 标签页、表单、高频切换列表 |
🚀 最佳实践口诀:
缓存组件要记牢,
Mounted只跑第一遭。数据刷新找
Activated,资源清理Deactivated跑。定时器、监听器,切换隐藏记得消。
希望这篇文档能帮你彻底搞定 Vue 3 中的 keep-alive 生命周期!如果有疑问,欢迎留言交流。👇
觉得有用吗?点赞、收藏、关注,获取更多 Vue 进阶干货! ❤️