<KeepAlive> 是 Vue 3 中一个极其重要的内置组件,它通过缓存组件实例来避免重复的销毁与重建,从而保留组件状态、提升性能。理解其缓存机制、配置选项和生命周期钩子,是构建高性能 Vue 应用的关键。
一、核心作用:保存组件状态
当一个组件被 <KeepAlive> 包裹时:
- 首次渲染:正常创建、挂载。
- 切换离开:不销毁,而是被缓存起来(失活)。
- 再次进入:不重新创建,而是从缓存中取出并激活。
保留了什么?
- 组件实例:
data、ref、reactive状态。 - DOM 状态:表单输入、滚动位置、动画状态。
- 子组件树:整个组件子树都被缓存。
对比普通组件
| 操作 | 普通组件 | <KeepAlive> 组件 |
|---|---|---|
| 首次进入 | created → mounted | created → mounted |
| 切换离开 | beforeUnmount → unmounted | onDeactivated |
| 再次进入 | created → mounted | onActivated |
二、实现原理:LRU 缓存机制
<KeepAlive> 的内部实现类似于一个 LRU (Least Recently Used) 缓存。
1. 核心数据结构
cache:Map类型,存储组件实例的 VNode。- Key:组件的唯一标识(通常由组件的
key或name生成)。 - Value:被缓存的组件的
VNode。
- Key:组件的唯一标识(通常由组件的
keys:Set类型,记录缓存的访问顺序(用于 LRU)。
2. 缓存流程
js
// 伪代码:渲染过程
function processKeepAlive(n1, n2, container, anchor) {
const instance = n2.component;
const key = getKey(n2); // 获取组件 key
if (n1) {
// 更新
const cached = cache.get(key);
if (cached && shouldInclude(cached)) {
// 命中缓存
instance.subTree = cached.subTree; // 复用缓存的虚拟 DOM
move(cached, container, anchor); // 移动到正确位置(Teleport 风格)
activate(instance); // 触发生命周期
} else {
// 未命中,重新渲染
renderComponent(n2);
cache.set(key, n2);
keys.add(key);
}
} else {
// 首次挂载
renderComponent(n2);
cache.set(key, n2);
keys.add(key);
}
}3. move() 操作
- 类似
Teleport,<KeepAlive>使用move()将缓存的 VNode 对应的 DOM 节点移动到正确位置。 - 这确保了 DOM 节点的复用,而不是重新创建。
三、配置选项的实现
<KeepAlive> 支持三个关键属性:include、exclude、max。
1. include 和 exclude
- 作用:根据组件的
name决定是否缓存。 - 类型:字符串、正则、数组。
vue
<KeepAlive
:include="/^My.*/"
:exclude="['Modal']"
>
<component :is="currentView" />
</KeepAlive>实现原理
js
function shouldInclude(componentName, pattern) {
if (typeof pattern === 'string') {
return pattern.split(',').includes(componentName);
} else if (pattern instanceof RegExp) {
return pattern.test(componentName);
} else if (Array.isArray(pattern)) {
return pattern.includes(componentName);
}
return false;
}
// 在缓存/取缓存时检查
if (include && !shouldInclude(name, include)) return false;
if (exclude && shouldInclude(name, exclude)) return false;- 优先级:
exclude优先于include。
2. max
- 作用:限制缓存的最大数量,防止内存泄漏。
- 实现:LRU 策略。
js
// 当缓存数量超过 max 时
if (max && cache.size > max) {
// 找到最久未使用的 key
const oldestKey = keys.values().next().value;
const oldestVNode = cache.get(oldestKey);
// 销毁最久未使用的组件
unmount(oldestVNode);
// 从缓存中移除
cache.delete(oldestKey);
keys.delete(oldestKey);
}- LRU 更新:每次组件被激活(
onActivated),其key会从keys中移除并重新添加到末尾,确保“最近使用”的排在后面。
四、生命周期钩子:onActivated 与 onDeactivated
由于 <KeepAlive> 组件不会被销毁,mounted/unmounted 钩子只在首次和最终销毁时触发。为此,Vue 提供了两个专用钩子。
1. onActivated()
- 触发时机:
- 组件从缓存中被取出并重新插入 DOM 时。
- 首次
mounted之后也会触发一次。
- 用途:
- 启动定时器、动画。
- 恢复事件监听。
- 触发数据更新(如轮询)。
js
import { onActivated, onDeactivated } from 'vue';
onActivated(() => {
console.log('组件被激活');
startTimer();
});2. onDeactivated()
- 触发时机:
- 组件被切换离开,放入缓存时。
- 用途:
- 清理定时器、取消订阅,避免内存泄漏。
- 暂停动画、音乐。
js
onDeactivated(() => {
console.log('组件被缓存(失活)');
clearInterval(timer);
// 不要调用 unmount,KeepAlive 会处理
});钩子执行顺序
js
// 首次进入
created → mounted → onActivated
// 切换离开
onDeactivated
// 再次进入
onActivated
// 最终销毁(如被 exclude 或 max 淘汰)
onDeactivated → beforeUnmount → unmounted五、与 v-if 和 v-show 的区别
| 特性 | v-if | v-show | <KeepAlive> |
|---|---|---|---|
| DOM 存在 | 销毁/重建 | 始终存在 | 缓存,DOM 可能被移除 |
| 状态保留 | 否 | 是 | 是 |
| 初始渲染开销 | 高(每次重建) | 低 | 高(首次),低(后续) |
| 适用场景 | 条件渲染,不常切换 | 频繁切换,简单元素 | 保留状态的复杂组件切换 |
v-show:通过display: none切换,DOM 始终在内存中,但组件实例不缓存,onDeactivated不会触发。<KeepAlive>:更智能,可以控制缓存策略。
六、最佳实践
合理使用
max:- 避免缓存过多组件导致内存压力。
- 结合业务场景设置合理的上限。
在
onDeactivated中清理资源:- 定时器、WebSocket、事件监听器必须清理。
- 否则会导致内存泄漏和后台运行。
配合
key使用:- 确保组件有稳定的
key,以便正确匹配缓存。
- 确保组件有稳定的
避免缓存重型组件:
- 如包含大量数据或复杂计算的组件,可能不值得缓存。
与路由结合:
js
// router.js
{
path: '/home',
component: () => import('./views/Home.vue'),
meta: { keepAlive: true }
}vue
<KeepAlive>
<router-view v-if="$route.meta.keepAlive" />
</KeepAlive>
<router-view v-if="!$route.meta.keepAlive" />七、总结
<KeepAlive> 的核心机制是:
- 缓存实例:通过
Map存储组件的VNode,保留状态。 - LRU 策略:
max限制缓存数量,keys维护访问顺序。 - 条件过滤:
include/exclude基于组件name决定是否缓存。 - 专用钩子:
onActivated/onDeactivated替代mounted/unmounted的部分职责。 - 高效移动:使用
move()操作复用 DOM 节点。
它是 Vue 实现高性能动态组件切换的基石。正确使用 <KeepAlive>,可以显著提升用户体验,避免不必要的重复渲染。