KeepAlive
What is KeepAlive
<KeepAlive> is a built-in component that caches and reuses component instances without destroying them. Normally, when components are switched, the old component is unmounted and its state is lost. However, by using KeepAlive, you can switch between components while preserving their state.

For example, imagine a screen with tab switching where you have a form being filled out in one tab. If you switch to another tab and come back, it would be frustrating if the input content disappeared. KeepAlive addresses this need to "preserve state"!
Main use cases:
- Tab switching: Preserve input content when switching tabs during form entry
- Routing: Preserve scroll position and input state during page navigation
- Performance: Avoid re-rendering frequently switched components
Basic Usage
<template>
<KeepAlive>
<component :is="currentTab" />
</KeepAlive>
</template>Implementation Overview
Props Definition
export interface KeepAliveProps {
include?: MatchPattern;
exclude?: MatchPattern;
max?: number | string;
}
type MatchPattern = string | RegExp | (string | RegExp)[];- include: Component names to cache (only those included are cached)
- exclude: Component names to exclude from caching (those included are not cached)
- max: Maximum number to cache (oldest ones are removed using LRU algorithm)
KeepAliveContext
The KeepAlive component has a special context for interacting with the renderer.
export interface KeepAliveContext extends ComponentInternalInstance {
renderer: KeepAliveRenderer;
activate: (
vnode: VNode,
container: any,
anchor: any | null,
parentComponent: ComponentInternalInstance | null,
) => void;
deactivate: (vnode: VNode) => void;
}- activate: Bring a cached component back to display
- deactivate: Hide a component and cache it
Core Logic Implementation
Cache Management
const cache: Map<any, VNode> = new Map();
const keys: Set<any> = new Set();
let current: VNode | null = null;
// Hidden container for storing inactive components
const storageContainer = instance.renderer.o.createElement("div");KeepAlive caches component VNodes using a cache Map. The keys Set is used for order management in the LRU (Least Recently Used) algorithm.
activate Function
Restores a component from the cache and displays it.
instance.activate = (vnode, container, anchor, _parentComponent) => {
const instance = vnode.component!;
// Move from hidden container to actual container
move(vnode, container, anchor);
// Apply any props changes
patch(instance.vnode, vnode, container, anchor, parentComponent);
queuePostFlushCb(() => {
instance.isDeactivated = false;
// Call onActivated hooks
if (instance.a) {
instance.a.forEach((hook: () => void) => hook());
}
});
};Key points:
- Move DOM from hidden container to target container
- Apply props changes via patch
- Call
onActivatedlifecycle hook
deactivate Function
Hides and caches the component.
instance.deactivate = (vnode: VNode) => {
// Move to hidden container (DOM is not removed)
move(vnode, storageContainer, null);
queuePostFlushCb(() => {
const instance = vnode.component!;
// Call onDeactivated hooks
if (instance.da) {
instance.da.forEach((hook: () => void) => hook());
}
instance.isDeactivated = true;
});
};Unlike normal unmounting, DOM elements are not removed but simply moved to the hidden container.

Components being hidden are moved to a "hideout" off-screen. When needed, they're simply retrieved from the "hideout", saving the trouble of rebuilding!
render Function
This is the core logic of KeepAlive.
return (): VNode | undefined => {
if (!slots.default) {
return undefined;
}
const children = slots.default();
const rawVNode = children[0];
// Don't cache if there are multiple children
if (children.length > 1) {
current = null;
return children as unknown as VNode;
}
// Return as-is if not a component
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 filtering
if (
(include && (!name || !matches(include, name))) ||
(exclude && name && matches(exclude, name))
) {
current = vnode;
return rawVNode;
}
// Determine cache key
const key = vnode.key == null ? comp : vnode.key;
const cachedVNode = cache.get(key);
if (cachedVNode) {
// Cache hit: restore state
vnode.el = cachedVNode.el;
vnode.component = cachedVNode.component;
vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;
// LRU: update order since recently used
keys.delete(key);
keys.add(key);
} else {
// New cache entry
keys.add(key);
// Remove oldest if exceeding max
if (max && keys.size > parseInt(max as string, 10)) {
pruneCacheEntry(keys.values().next().value);
}
}
// Set flag to let renderer recognize KeepAlive
vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;
current = vnode;
return vnode;
};Control via ShapeFlags
KeepAlive coordinates with the renderer using ShapeFlags.
// This component should be managed by KeepAlive
vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;
// This component was restored from cache
vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;The renderer checks these flags and calls activate/deactivate instead of normal mount/unmount.
include/exclude Matching
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;
}Patterns support the following formats:
- String (comma-separated):
"ComponentA,ComponentB" - Regular expression:
/^Tab/ - Array:
["ComponentA", /^Tab/]
Cache Pruning
function pruneCacheEntry(key: any): void {
const cached = cache.get(key) as VNode;
// Unmount if not currently displayed
if (!current || !isSameVNodeType(cached, current)) {
unmount(cached);
} else if (current) {
// Only reset flags if currently displayed
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;
}Lifecycle Hooks
Components managed by KeepAlive can use additional lifecycle hooks:
- onActivated: When the component becomes active
- onDeactivated: When the component becomes inactive
import { onActivated, onDeactivated } from 'vue'
export default {
setup() {
onActivated(() => {
console.log('activated!')
})
onDeactivated(() => {
console.log('deactivated!')
})
}
}Usage Examples
Basic Usage
<template>
<KeepAlive>
<component :is="currentComponent" />
</KeepAlive>
</template>Using include/exclude
<template>
<!-- Cache only ComponentA and ComponentB -->
<KeepAlive include="ComponentA,ComponentB">
<component :is="currentComponent" />
</KeepAlive>
<!-- Cache everything except ComponentC -->
<KeepAlive exclude="ComponentC">
<component :is="currentComponent" />
</KeepAlive>
<!-- Match with regular expression -->
<KeepAlive :include="/^Tab/">
<component :is="currentComponent" />
</KeepAlive>
</template>Using max
<template>
<!-- Cache up to 10 components (LRU) -->
<KeepAlive :max="10">
<component :is="currentComponent" />
</KeepAlive>
</template>Integration with Renderer
KeepAlive works in close coordination with the renderer.
KeepAlive Detection in mountComponent
// packages/runtime-core/src/renderer.ts
const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent) => {
const instance: ComponentInternalInstance = (
initialVNode.component = createComponentInstance(initialVNode, parentComponent)
);
// For KeepAlive components, inject the renderer
if (isKeepAlive(initialVNode)) {
(instance as KeepAliveContext).renderer = {
p: patch, // patch function
m: move, // DOM move function
um: unmount, // unmount function
o: options, // host options (createElement, etc.)
};
}
// ... normal mount processing
};KEPT_ALIVE Check in processComponent
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null = null,
) => {
if (n1 == null) {
// New mount
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
// Restore from cache: call activate
(parentComponent as KeepAliveContext).activate(
n2,
container,
anchor,
parentComponent as ComponentInternalInstance
);
} else {
// Normal mount
mountComponent(n2, container, anchor, parentComponent);
}
} else {
updateComponent(n1, n2);
}
};SHOULD_KEEP_ALIVE Check in unmount
const unmount: UnmountFn = (vnode, parentComponent?: ComponentInternalInstance) => {
const { type, shapeFlag, children } = vnode;
// Components under KeepAlive management are deactivated, not removed
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
(parentComponent as KeepAliveContext).deactivate(vnode);
return;
}
// Normal unmount processing
if (shapeFlag & ShapeFlags.COMPONENT) {
unmountComponent(vnode.component!);
}
// ...
};Processing Flow
Initial Mount:
KeepAlive render
→ Get child from slot
→ Not in cache → Add to keys
→ Set COMPONENT_SHOULD_KEEP_ALIVE flag
→ Return vnode
↓
processComponent
→ No COMPONENT_KEPT_ALIVE → mountComponent
→ isKeepAlive(vnode) → Inject renderer
→ Normal component mount
Restore from Cache:
KeepAlive render
→ Get child from slot
→ Cache hit → Reuse el/component
→ Add COMPONENT_KEPT_ALIVE flag
→ Update keys order (LRU)
→ Return vnode
↓
processComponent
→ COMPONENT_KEPT_ALIVE present
→ Call parentComponent.activate()
↓
activate
→ Move from hidden container to real container
→ Apply props changes via patch
→ instance.isDeactivated = false
→ Call onActivated hook
Deactivation:
unmount
→ COMPONENT_SHOULD_KEEP_ALIVE present
→ Call parentComponent.deactivate()
↓
deactivate
→ Move to hidden container (DOM is not removed)
→ instance.isDeactivated = true
→ Call onDeactivated hook
→ Kept in cache
Components cached by KeepAlive remain in memory. Caching too many can strain memory, so set an upper limit with the max property. It's automatically managed with LRU (removing least recently used items)!
Summary
The KeepAlive implementation consists of the following elements:
- Cache System: LRU cache using Map and Set
- Hidden Container: Holds inactive DOM (
createElement("div")) - activate/deactivate: DOM movement and lifecycle management
- ShapeFlags: Coordination with renderer
COMPONENT_SHOULD_KEEP_ALIVE: Call deactivate during unmountCOMPONENT_KEPT_ALIVE: Call activate during mount
- Renderer Injection: KeepAlive holds references to patch/move/unmount functions
- include/exclude/max: Flexible cache control
KeepAlive is a powerful feature that improves performance while preserving component state, but there's a trade-off with memory usage, so setting an appropriate max value is important.

It's a simple idea of "hiding instead of removing" components, but the implementation with renderer coordination and LRU cache is quite deep!
Source code up to this point: chibivue (GitHub)
