Special handling of the KeepAlive component
Special Handling of the KeepAlive Component
In Vue 3, KeepAlive is a built-in component used to cache inactive component instances, avoiding performance overhead caused by repeated rendering. It manages the cache using the LRU algorithm while providing flexible configuration options such as include/exclude. The implementation of this component involves multiple core systems of Vue, including component instance management, virtual DOM comparison, and lifecycle hook handling.
Basic Working Principle
The core mechanism of KeepAlive is to maintain a cache object to store wrapped component instances. When components are switched, KeepAlive checks whether a matching instance exists in the cache:
// Simplified cache-checking logic
const cached = cache.get(key)
if (cached) {
// Restore instance from cache
vnode.component = cached
// Mark as mounted state
vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE
} else {
// Create a new instance
mountComponent(vnode, container, anchor)
}
The cache key generation rule considers the component's name
property and the currently active slot, ensuring components with different "identities" are cached separately:
function getComponentName(Component) {
return isFunction(Component)
? Component.displayName || Component.name
: Component.name
}
Special Handling in Virtual DOM
KeepAlive requires special handling at the virtual DOM level, primarily reflected in the shapeFlag
marker. When detecting a component wrapped by KeepAlive, Vue sets a special flag:
// During the patching process
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
// Execute KeepAlive-specific logic
parentComponent.ctx.activate(vnode, container, anchor)
}
These flags affect the subsequent diff algorithm behavior, enabling Vue to recognize and correctly handle cached component instances.
Lifecycle Hook Handling
The KeepAlive component modifies conventional lifecycle behavior by introducing two unique lifecycle hooks:
activated
- Called when a component cached by KeepAlive is activated.deactivated
- Called when a component cached by KeepAlive is deactivated.
In implementation, KeepAlive hijacks the component's actual mount/unmount methods:
// Pseudocode showing hook injection
const instance = vnode.component
const { mounted, unmount } = instance
instance.mounted = () => {
mounted.call(instance)
queuePostRenderEffect(() => {
instance.isDeactivated = false
callHook(instance, 'activated')
})
}
instance.unmount = () => {
callHook(instance, 'deactivated')
// Not a real unmount, just moves to a hidden container
move(vnode, storageContainer)
}
LRU Cache Management Strategy
KeepAlive uses the LRU (Least Recently Used) algorithm by default to manage the cache. When the cache size exceeds the max
setting, the least recently used instance is automatically evicted:
// Simplified LRU implementation
function pruneCacheEntry(key) {
const cached = cache.get(key)
if (!current || cached.type !== current.type) {
unmount(cached)
cache.delete(key)
keys.delete(key)
}
}
// Update usage order when accessing the cache
function accessCache(key) {
keys.delete(key)
keys.add(key)
if (max && keys.size > max) {
pruneCacheEntry(keys.values().next().value)
}
}
Developers can control the maximum number of cached instances via the max
property, which is unlimited by default.
Component State Retention Mechanism
KeepAlive not only saves component instances but also preserves their complete state, including:
- Reactive data state
- Registered side effects
- Internal DOM state of the component
This is achieved through a special storage container:
// Hidden container creation
const storageContainer = createElement('div')
// Move to hidden container when deactivated
function deactivate(vnode) {
move(vnode, storageContainer)
}
// Move back to DOM when activated
function activate(vnode, container, anchor) {
move(vnode, container, anchor)
}
This design avoids performance overhead from repeatedly creating components while ensuring the state is fully retained.
include/exclude Matching Rules
KeepAlive provides include
and exclude
properties to precisely control which components should be cached:
// Matching logic implementation
function matches(pattern, name) {
if (isArray(pattern)) {
return pattern.some(p => matches(p, name))
} else if (isString(pattern)) {
return pattern.split(',').includes(name)
} else if (isRegExp(pattern)) {
return pattern.test(name)
}
return false
}
These can be flexibly combined in usage:
<KeepAlive :include="['ComponentA', /^ComponentB/]" :exclude="['ComponentC']">
<component :is="currentComponent" />
</KeepAlive>
Collaboration with Suspense
In Vue 3, KeepAlive requires special handling to work with Suspense components. When nested together, KeepAlive waits for Suspense to resolve:
// Async component handling
if (isAsyncWrapper(vnode)) {
// Wait for async component resolution
return vnode.type.__asyncLoader().then(() => {
// Continue KeepAlive processing after resolution
pendingCacheKey = key
return instance.update()
})
}
This ensures async components can also be correctly cached and restored.
Special Handling for Server-Side Rendering
In SSR environments, KeepAlive behaves differently:
- Does not actually cache component instances
- Still generates correct client-side markers
- Restores cache state during hydration
Simplified handling during SSR:
if (isSSR) {
// Only renders child content without actual caching
return () => slots.default()
}
The client restores the corresponding component state during hydration based on cache markers sent by the server.
Performance Optimization Strategies
KeepAlive implements multiple performance optimizations:
- Lazy unmounting strategy: Avoids frequent DOM operations
- Batch node movement: Reduces layout thrashing
- Memory reclamation mechanism: Automatically cleans unnecessary caches
A key optimization is using requestPostAnimationFrame
to schedule node movement:
function move(vnode, container, anchor) {
if (isReusing) {
// Optimized path for reuse cases
requestPostAnimationFrame(() => {
insert(vnode.el, container, anchor)
})
} else {
insert(vnode.el, container, anchor)
}
}
Custom Cache Strategies
Advanced usage allows overriding the default cache strategy via provide/inject
:
// Custom cache implementation
const CustomCache = {
get(key) { /*...*/ },
set(key, value) { /*...*/ },
delete(key) { /*...*/ }
}
provide(KeepAliveContext, {
renderer: internals,
cache: CustomCache
})
This enables developers to implement more complex cache strategies based on time, memory usage, etc.
Edge Case Handling
KeepAlive must handle various edge cases:
- Cache updates when dynamic component names change
- Distinguishing between the same component with different slot content
- Cache invalidation during hot updates
- Handling component definition changes
For example, logic for handling dynamic name changes:
watch(() => getComponentName(component), (name, oldName) => {
if (name && name !== oldName) {
// Update cache key when name changes
pruneCacheEntry(cache, key)
}
})
Debugging Support
In development mode, KeepAlive provides rich debugging information:
- Cache hit logs
- LRU eviction logs
- Component activation/deactivation tracking
Detailed cache state can be viewed via Vue Devtools, including:
- Current cached component list
- Cache time for each component
- Recent access order
Collaboration with Transition Component
When used with the Transition component, KeepAlive requires additional handling for animation phases:
// Handle transition animations
if (hasTransition) {
// Wait for transition to complete before moving DOM
vnode.transition.afterLeave = () => {
move(vnode, storageContainer)
}
} else {
move(vnode, storageContainer)
}
This ensures animations can play completely without being interrupted by DOM movement.
Memory Management Considerations
While KeepAlive improves performance, improper use may cause memory issues:
- Avoid caching large components
- Set reasonable
max
limits - Consider manually clearing caches for dynamic components
Specific caches can be manually cleared via instance API:
const keepAliveInstance = ref()
// Clear cache for specific component
function clearCache(name) {
keepAliveInstance.value?.__pruneCache(name)
}
Source Code Structure Analysis
KeepAlive's implementation is mainly distributed across several key parts:
- Component definition -
runtime-core/src/components/KeepAlive.ts
- Cache management -
runtime-core/src/renderer.ts
- Lifecycle handling -
runtime-core/src/component.ts
The core implementation spans about 500 lines of code but involves multiple core systems of Vue, requiring coordination between the renderer, reactivity system, and component lifecycle.
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn