KeepAlive
什么是 KeepAlive
<KeepAlive> 是一个内置组件,它可以缓存并复用组件实例而不销毁它们.通常,当组件切换时,旧组件会被卸载,状态也会丢失.但是,通过使用 KeepAlive,您可以在切换组件时保留它们的状态.

例如,想象一个带有标签页切换的界面,其中一个标签页正在填写表单. 如果您切换到另一个标签页再切换回来,输入的内容消失了会很令人沮丧. KeepAlive 就是为了满足这种"保留状态"的需求!
主要用例:
- 标签页切换:在表单输入过程中切换标签页时保留输入内容
- 路由:在页面导航期间保留滚动位置和输入状态
- 性能:避免频繁切换的组件重新渲染
基本用法
<template>
<KeepAlive>
<component :is="currentTab" />
</KeepAlive>
</template>实现概述
Props 定义
export interface KeepAliveProps {
include?: MatchPattern;
exclude?: MatchPattern;
max?: number | string;
}
type MatchPattern = string | RegExp | (string | RegExp)[];- include:要缓存的组件名称(只有包含的才会被缓存)
- exclude:要排除缓存的组件名称(包含的不会被缓存)
- max:缓存的最大数量(使用 LRU 算法删除最旧的)
KeepAliveContext
KeepAlive 组件有一个用于与渲染器交互的特殊上下文.
export interface KeepAliveContext extends ComponentInternalInstance {
renderer: KeepAliveRenderer;
activate: (
vnode: VNode,
container: any,
anchor: any | null,
parentComponent: ComponentInternalInstance | null,
) => void;
deactivate: (vnode: VNode) => void;
}- activate:将缓存的组件恢复显示
- deactivate:隐藏组件并缓存它
核心逻辑实现
缓存管理
const cache: Map<any, VNode> = new Map();
const keys: Set<any> = new Set();
let current: VNode | null = null;
// 用于存储非活动组件的隐藏容器
const storageContainer = instance.renderer.o.createElement("div");KeepAlive 使用 cache Map 来缓存组件的 VNode.keys Set 用于 LRU(最近最少使用)算法的顺序管理.
activate 函数
从缓存中恢复组件并显示它.
instance.activate = (vnode, container, anchor, _parentComponent) => {
const instance = vnode.component!;
// 从隐藏容器移动到实际容器
move(vnode, container, anchor);
// 应用任何 props 变化
patch(instance.vnode, vnode, container, anchor, parentComponent);
queuePostFlushCb(() => {
instance.isDeactivated = false;
// 调用 onActivated 钩子
if (instance.a) {
instance.a.forEach((hook: () => void) => hook());
}
});
};关键点:
- 将 DOM 从隐藏容器移动到目标容器
- 通过 patch 应用 props 变化
- 调用
onActivated生命周期钩子
deactivate 函数
隐藏并缓存组件.
instance.deactivate = (vnode: VNode) => {
// 移动到隐藏容器(DOM 不会被删除)
move(vnode, storageContainer, null);
queuePostFlushCb(() => {
const instance = vnode.component!;
// 调用 onDeactivated 钩子
if (instance.da) {
instance.da.forEach((hook: () => void) => hook());
}
instance.isDeactivated = true;
});
};与正常卸载不同,DOM 元素不会被删除,只是移动到隐藏容器中.

被隐藏的组件会被移动到屏幕外的"藏身处". 需要时,只需从"藏身处"取出即可,省去了重建的麻烦!
render 函数
这是 KeepAlive 的核心逻辑.
return (): VNode | undefined => {
if (!slots.default) {
return undefined;
}
const children = slots.default();
const rawVNode = children[0];
// 如果有多个子节点则不缓存
if (children.length > 1) {
current = null;
return children as unknown as VNode;
}
// 如果不是组件则直接返回
if (
!(rawVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) &&
!(rawVNode.shapeFlag & ShapeFlags.COMPONENT)
) {
current = null;
return rawVNode;
}
let vnode = rawVNode;
const comp = vnode.type as any;
const name = getComponentName(comp);
const { include, exclude, max } = props;
// include/exclude 过滤
if (
(include && (!name || !matches(include, name))) ||
(exclude && name && matches(exclude, name))
) {
current = vnode;
return rawVNode;
}
// 确定缓存键
const key = vnode.key == null ? comp : vnode.key;
const cachedVNode = cache.get(key);
if (cachedVNode) {
// 缓存命中:恢复状态
vnode.el = cachedVNode.el;
vnode.component = cachedVNode.component;
vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;
// LRU:因为最近使用所以更新顺序
keys.delete(key);
keys.add(key);
} else {
// 新缓存条目
keys.add(key);
// 如果超过最大值则删除最旧的
if (max && keys.size > parseInt(max as string, 10)) {
pruneCacheEntry(keys.values().next().value);
}
}
// 设置标志让渲染器识别 KeepAlive
vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;
current = vnode;
return vnode;
};通过 ShapeFlags 控制
KeepAlive 使用 ShapeFlags 与渲染器协调.
// 此组件应由 KeepAlive 管理
vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;
// 此组件是从缓存恢复的
vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;渲染器检查这些标志,并调用 activate/deactivate 而不是正常的 mount/unmount.
include/exclude 匹配
function matches(pattern: MatchPattern, name: string): boolean {
if (isArray(pattern)) {
return pattern.some((p: string | RegExp) => matches(p, name));
} else if (isString(pattern)) {
return pattern.split(",").includes(name);
} else if (pattern instanceof RegExp) {
return pattern.test(name);
}
return false;
}模式支持以下格式:
- 字符串(逗号分隔):
"ComponentA,ComponentB" - 正则表达式:
/^Tab/ - 数组:
["ComponentA", /^Tab/]
缓存清理
function pruneCacheEntry(key: any): void {
const cached = cache.get(key) as VNode;
// 如果当前未显示则卸载
if (!current || !isSameVNodeType(cached, current)) {
unmount(cached);
} else if (current) {
// 如果当前正在显示则只重置标志
resetShapeFlag(current);
}
cache.delete(key);
keys.delete(key);
}
function resetShapeFlag(vnode: VNode): void {
vnode.shapeFlag &= ~ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;
vnode.shapeFlag &= ~ShapeFlags.COMPONENT_KEPT_ALIVE;
}生命周期钩子
由 KeepAlive 管理的组件可以使用额外的生命周期钩子:
- onActivated:当组件变为活动状态时
- onDeactivated:当组件变为非活动状态时
import { onActivated, onDeactivated } from 'vue'
export default {
setup() {
onActivated(() => {
console.log('activated!')
})
onDeactivated(() => {
console.log('deactivated!')
})
}
}使用示例
基本用法
<template>
<KeepAlive>
<component :is="currentComponent" />
</KeepAlive>
</template>使用 include/exclude
<template>
<!-- 只缓存 ComponentA 和 ComponentB -->
<KeepAlive include="ComponentA,ComponentB">
<component :is="currentComponent" />
</KeepAlive>
<!-- 缓存除 ComponentC 以外的所有组件 -->
<KeepAlive exclude="ComponentC">
<component :is="currentComponent" />
</KeepAlive>
<!-- 使用正则表达式匹配 -->
<KeepAlive :include="/^Tab/">
<component :is="currentComponent" />
</KeepAlive>
</template>使用 max
<template>
<!-- 最多缓存 10 个组件(LRU) -->
<KeepAlive :max="10">
<component :is="currentComponent" />
</KeepAlive>
</template>与渲染器的集成
KeepAlive 与渲染器紧密协调工作.
mountComponent 中的 KeepAlive 检测
// packages/runtime-core/src/renderer.ts
const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent) => {
const instance: ComponentInternalInstance = (
initialVNode.component = createComponentInstance(initialVNode, parentComponent)
);
// 对于 KeepAlive 组件,注入渲染器
if (isKeepAlive(initialVNode)) {
(instance as KeepAliveContext).renderer = {
p: patch, // patch 函数
m: move, // DOM 移动函数
um: unmount, // 卸载函数
o: options, // 宿主选项(createElement 等)
};
}
// ... 正常的挂载处理
};processComponent 中的 KEPT_ALIVE 检查
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null = null,
) => {
if (n1 == null) {
// 新挂载
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
// 从缓存恢复:调用 activate
(parentComponent as KeepAliveContext).activate(
n2,
container,
anchor,
parentComponent as ComponentInternalInstance
);
} else {
// 正常挂载
mountComponent(n2, container, anchor, parentComponent);
}
} else {
updateComponent(n1, n2);
}
};unmount 中的 SHOULD_KEEP_ALIVE 检查
const unmount: UnmountFn = (vnode, parentComponent?: ComponentInternalInstance) => {
const { type, shapeFlag, children } = vnode;
// KeepAlive 管理下的组件会被停用而不是删除
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
(parentComponent as KeepAliveContext).deactivate(vnode);
return;
}
// 正常的卸载处理
if (shapeFlag & ShapeFlags.COMPONENT) {
unmountComponent(vnode.component!);
}
// ...
};处理流程
初次挂载:
KeepAlive render
→ 从插槽获取子节点
→ 不在缓存中 → 添加到 keys
→ 设置 COMPONENT_SHOULD_KEEP_ALIVE 标志
→ 返回 vnode
↓
processComponent
→ 没有 COMPONENT_KEPT_ALIVE → mountComponent
→ isKeepAlive(vnode) → 注入渲染器
→ 正常组件挂载
从缓存恢复:
KeepAlive render
→ 从插槽获取子节点
→ 缓存命中 → 复用 el/component
→ 添加 COMPONENT_KEPT_ALIVE 标志
→ 更新 keys 顺序(LRU)
→ 返回 vnode
↓
processComponent
→ 存在 COMPONENT_KEPT_ALIVE
→ 调用 parentComponent.activate()
↓
activate
→ 从隐藏容器移动到实际容器
→ 通过 patch 应用 props 变化
→ instance.isDeactivated = false
→ 调用 onActivated 钩子
停用:
unmount
→ 存在 COMPONENT_SHOULD_KEEP_ALIVE
→ 调用 parentComponent.deactivate()
↓
deactivate
→ 移动到隐藏容器(DOM 不会被删除)
→ instance.isDeactivated = true
→ 调用 onDeactivated 钩子
→ 保留在缓存中
被 KeepAlive 缓存的组件会一直保留在内存中. 缓存太多会占用内存,所以请使用 max 属性设置上限. 它会通过 LRU(删除最近最少使用的项目)自动管理!
总结
KeepAlive 的实现由以下元素组成:
- 缓存系统:使用 Map 和 Set 的 LRU 缓存
- 隐藏容器:保存非活动的 DOM(
createElement("div")) - activate/deactivate:DOM 移动和生命周期管理
- ShapeFlags:与渲染器协调
COMPONENT_SHOULD_KEEP_ALIVE:卸载时调用 deactivateCOMPONENT_KEPT_ALIVE:挂载时调用 activate
- 渲染器注入:KeepAlive 持有 patch/move/unmount 函数的引用
- include/exclude/max:灵活的缓存控制
KeepAlive 是一个强大的功能,可以在保留组件状态的同时提高性能,但需要权衡内存使用,因此设置适当的 max 值很重要.

"隐藏而不是删除"组件是一个简单的想法, 但与渲染器协调和 LRU 缓存的实现相当深入!
到此为止的源代码: chibivue (GitHub)
