KeepAlive 是 Vue3 中的一个内置组件,用于缓存动态组件,避免重复渲染和销毁,从而提升应用性能。当组件在 KeepAlive 内切换时,被切换的组件不会被销毁,而是被缓存起来,下次激活时直接从缓存中恢复。
KeepAlive 使用 ShapeFlags 枚举来标记组件的状态:
typescriptexport enum ShapeFlags {
// ... 其他标志
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8, // 组件应该被 keep-alive
COMPONENT_KEPT_ALIVE = 1 << 9, // 组件已被 keep-alive
}
typescriptexport interface KeepAliveProps {
include?: string | RegExp | (string | RegExp)[] // 包含的组件名
exclude?: string | RegExp | (string | RegExp)[] // 排除的组件名
max?: number | string // 最大缓存数量
}
typescriptconst KeepAliveImpl: ComponentOptions = {
name: `KeepAlive`,
__isKeepAlive: true, // 特殊标记,用于渲染器识别
props: {
include: [String, RegExp, Array],
exclude: [String, RegExp, Array],
max: [String, Number],
},
setup(props: KeepAliveProps, { slots }: SetupContext) {
// 核心实现
}
}
KeepAlive 使用 Map 和 Set 来管理缓存:
typescripttype CacheKey = PropertyKey | ConcreteComponent
type Cache = Map<CacheKey, VNode>
type Keys = Set<CacheKey>
const cache: Cache = new Map() // 缓存 VNode
const keys: Keys = new Set() // 记录缓存键的顺序
let current: VNode | null = null // 当前活动的 VNode
在组件挂载时,渲染器会向 KeepAlive 实例注入渲染相关的方法:
typescript// renderer.ts 中的 mountComponent 函数
if (isKeepAlive(initialVNode)) {
(instance.ctx as KeepAliveContext).renderer = internals
}
typescriptconst sharedContext = instance.ctx as KeepAliveContext
// 注入 activate 和 deactivate 方法
sharedContext.activate = (vnode, container, anchor, namespace, optimized) => {
const instance = vnode.component!
move(vnode, container, anchor, MoveType.ENTER, parentSuspense)
// 更新组件并调用激活钩子
patch(/* ... */)
queuePostRenderEffect(() => {
instance.isDeactivated = false
if (instance.a) {
invokeArrayFns(instance.a)
}
}, parentSuspense)
}
sharedContext.deactivate = (vnode: VNode) => {
const instance = vnode.component!
invalidateMount(instance.m)
invalidateMount(instance.a)
move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)
queuePostRenderEffect(() => {
if (instance.da) {
invokeArrayFns(instance.da)
}
instance.isDeactivated = true
}, parentSuspense)
}
typescriptreturn () => {
pendingCacheKey = null
if (!slots.default) {
return (current = null)
}
const children = slots.default()
const rawVNode = children[0]
// 验证子节点
if (children.length > 1) {
if (__DEV__) {
warn(`KeepAlive should contain exactly one component child.`)
}
current = null
return children
}
// 获取内部子节点(处理 Suspense)
let vnode = getInnerChild(rawVNode)
// 处理注释节点
if (vnode.type === Comment) {
current = null
return vnode
}
const comp = vnode.type as ConcreteComponent
// 获取组件名称(处理异步组件)
const name = getComponentName(
isAsyncWrapper(vnode)
? (vnode.type as ComponentOptions).__asyncResolved || {}
: comp,
)
const { include, exclude, max } = props
// 组件过滤检查
if (
(include && (!name || !matches(include, name))) ||
(exclude && name && matches(exclude, name))
) {
vnode.shapeFlag &= ~ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
current = vnode
return rawVNode
}
const key = vnode.key == null ? comp : vnode.key
const cachedVNode = cache.get(key)
// 克隆已存在的 vnode
if (vnode.el) {
vnode = cloneVNode(vnode)
if (rawVNode.shapeFlag & ShapeFlags.SUSPENSE) {
rawVNode.ssContent = vnode
}
}
pendingCacheKey = key
if (cachedVNode) {
// 从缓存恢复
vnode.el = cachedVNode.el
vnode.component = cachedVNode.component
// 处理过渡动画
if (vnode.transition) {
setTransitionHooks(vnode, vnode.transition!)
}
// 设置缓存标志
vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE
// 更新最近使用顺序
keys.delete(key)
keys.add(key)
} else {
// 新增缓存
keys.add(key)
// LRU 缓存淘汰
if (max && keys.size > parseInt(max as string, 10)) {
pruneCacheEntry(keys.values().next().value!)
}
}
// 标记为需要 keep-alive
vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
current = vnode
return isSuspense(rawVNode.type) ? rawVNode : vnode
}
typescriptfunction pruneCache(filter: (name: string) => boolean) {
cache.forEach((vnode, key) => {
const name = getComponentName(vnode.type as ConcreteComponent)
if (name && !filter(name)) {
pruneCacheEntry(key)
}
})
}
function pruneCacheEntry(key: CacheKey) {
const cached = cache.get(key) as VNode
if (cached && (!current || !isSameVNodeType(cached, current))) {
unmount(cached)
} else if (current) {
resetShapeFlag(current)
}
cache.delete(key)
keys.delete(key)
}
typescriptwatch(
() => [props.include, props.exclude],
([include, exclude]) => {
include && pruneCache(name => matches(include, name))
exclude && pruneCache(name => !matches(exclude, name))
},
{ flush: 'post', deep: true }
)
typescriptexport function onActivated(
hook: Function,
target?: ComponentInternalInstance | null,
): void {
registerKeepAliveHook(hook, LifecycleHooks.ACTIVATED, target)
}
export function onDeactivated(
hook: Function,
target?: ComponentInternalInstance | null,
): void {
registerKeepAliveHook(hook, LifecycleHooks.DEACTIVATED, target)
}
function registerKeepAliveHook(
hook: Function & { __wdc?: Function },
type: LifecycleHooks,
target: ComponentInternalInstance | null = currentInstance,
) {
const wrappedHook =
hook.__wdc ||
(hook.__wdc = () => {
// 检查是否在非激活分支中
let current: ComponentInternalInstance | null = target
while (current) {
if (current.isDeactivated) {
return
}
current = current.parent
}
return hook()
})
injectHook(type, wrappedHook, target)
}
typescript// renderer.ts 中的 processComponent 函数
if (n1 == null) {
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
// 调用激活方法
(parentComponent!.ctx as KeepAliveContext).activate(
n2,
container,
anchor,
namespace,
optimized,
)
} else {
// 正常挂载组件
mountComponent(/* ... */)
}
} else {
// 更新组件
updateComponent(n1, n2, optimized)
}
typescript// 组件挂载完成后的 activated 钩子处理
if (
initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE ||
(parent &&
isAsyncWrapper(parent.vnode) &&
parent.vnode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE)
) {
instance.a && queuePostRenderEffect(instance.a, parentSuspense)
}
typescriptconst key = vnode.key == null ? comp : vnode.key
如果没有显式指定 key,则使用组件构造函数作为缓存键。
typescriptif (vnode.el) {
vnode = cloneVNode(vnode)
if (rawVNode.shapeFlag & ShapeFlags.SUSPENSE) {
rawVNode.ssContent = vnode
}
}
克隆 VNode 是为了防止修改原始的 VNode 结构。
当缓存数量超过 max 限制时,会淘汰最早使用的组件:
typescriptif (max && keys.size > parseInt(max as string, 10)) {
pruneCacheEntry(keys.values().next().value!)
}
typescriptconst name = getComponentName(
isAsyncWrapper(vnode)
? (vnode.type as ComponentOptions).__asyncResolved || {}
: comp,
)
对于异步组件,使用已解析的内部组件名称进行匹配。
通过 ShapeFlags.COMPONENT_KEPT_ALIVE 标志,渲染器可以识别哪些组件是从缓存中恢复的,避免重新创建 DOM 元素。
使用 queuePostRenderEffect 将钩子调用推迟到渲染完成后执行,确保 DOM 操作的批量处理。
通过 max 属性和 LRU 算法控制缓存大小,防止内存泄漏。


本文作者:繁星
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!