2026-08-20
Vue
0

目录

1.相关概念
1.1 ShapeFlags 标志位
1.2 KeepAlive 组件属性
2. 实现原理
2.1 KeepAlive 组件结构
2.2 缓存机制
3. 核心实现流程
3.1 组件挂载阶段
3.1.1 渲染器注入
3.1.2 初始化上下文
3.2 渲染函数实现
3.3 缓存管理
3.3.1 缓存清理
3.3.2 属性变化监听
3.4 生命周期钩子
3.4.1 activated 和 deactivated
4. 渲染器处理逻辑
4.1 组件处理流程
4.2 挂载后处理
5. 关键技术细节
5.1 缓存键生成策略
5.2 VNode 克隆机制
5.3 LRU 缓存淘汰
5.4 异步组件处理
6. 性能优化
6.1 避免不必要的重新渲染
6.2 批量更新
6.3 内存管理
7. 使用注意事项
7.1 适用场景
7.2 不适用场景
7.3 常见问题

KeepAlive 是 Vue3 中的一个内置组件,用于缓存动态组件,避免重复渲染和销毁,从而提升应用性能。当组件在 KeepAlive 内切换时,被切换的组件不会被销毁,而是被缓存起来,下次激活时直接从缓存中恢复。

1.相关概念

1.1 ShapeFlags 标志位

KeepAlive 使用 ShapeFlags 枚举来标记组件的状态:

typescript
export enum ShapeFlags { // ... 其他标志 COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8, // 组件应该被 keep-alive COMPONENT_KEPT_ALIVE = 1 << 9, // 组件已被 keep-alive }

1.2 KeepAlive 组件属性

typescript
export interface KeepAliveProps { include?: string | RegExp | (string | RegExp)[] // 包含的组件名 exclude?: string | RegExp | (string | RegExp)[] // 排除的组件名 max?: number | string // 最大缓存数量 }

2. 实现原理

2.1 KeepAlive 组件结构

typescript
const KeepAliveImpl: ComponentOptions = { name: `KeepAlive`, __isKeepAlive: true, // 特殊标记,用于渲染器识别 props: { include: [String, RegExp, Array], exclude: [String, RegExp, Array], max: [String, Number], }, setup(props: KeepAliveProps, { slots }: SetupContext) { // 核心实现 } }

2.2 缓存机制

KeepAlive 使用 Map 和 Set 来管理缓存:

typescript
type 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

3. 核心实现流程

3.1 组件挂载阶段

3.1.1 渲染器注入

在组件挂载时,渲染器会向 KeepAlive 实例注入渲染相关的方法:

typescript
// renderer.ts 中的 mountComponent 函数 if (isKeepAlive(initialVNode)) { (instance.ctx as KeepAliveContext).renderer = internals }

3.1.2 初始化上下文

typescript
const 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) }

3.2 渲染函数实现

typescript
return () => { 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 }

3.3 缓存管理

3.3.1 缓存清理

typescript
function 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) }

3.3.2 属性变化监听

typescript
watch( () => [props.include, props.exclude], ([include, exclude]) => { include && pruneCache(name => matches(include, name)) exclude && pruneCache(name => !matches(exclude, name)) }, { flush: 'post', deep: true } )

3.4 生命周期钩子

3.4.1 activated 和 deactivated

typescript
export 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) }

4. 渲染器处理逻辑

4.1 组件处理流程

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) }

4.2 挂载后处理

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) }

5. 关键技术细节

5.1 缓存键生成策略

typescript
const key = vnode.key == null ? comp : vnode.key

如果没有显式指定 key,则使用组件构造函数作为缓存键。

5.2 VNode 克隆机制

typescript
if (vnode.el) { vnode = cloneVNode(vnode) if (rawVNode.shapeFlag & ShapeFlags.SUSPENSE) { rawVNode.ssContent = vnode } }

克隆 VNode 是为了防止修改原始的 VNode 结构。

5.3 LRU 缓存淘汰

当缓存数量超过 max 限制时,会淘汰最早使用的组件:

typescript
if (max && keys.size > parseInt(max as string, 10)) { pruneCacheEntry(keys.values().next().value!) }

5.4 异步组件处理

typescript
const name = getComponentName( isAsyncWrapper(vnode) ? (vnode.type as ComponentOptions).__asyncResolved || {} : comp, )

对于异步组件,使用已解析的内部组件名称进行匹配。

6. 性能优化

6.1 避免不必要的重新渲染

通过 ShapeFlags.COMPONENT_KEPT_ALIVE 标志,渲染器可以识别哪些组件是从缓存中恢复的,避免重新创建 DOM 元素。

6.2 批量更新

使用 queuePostRenderEffect 将钩子调用推迟到渲染完成后执行,确保 DOM 操作的批量处理。

6.3 内存管理

通过 max 属性和 LRU 算法控制缓存大小,防止内存泄漏。

7. 使用注意事项

7.1 适用场景

  • 频繁切换的组件(如标签页、路由组件)
  • 创建成本较高的组件
  • 需要保持组件状态的场景

7.2 不适用场景

  • 简单的展示组件
  • 数据实时性要求很高的组件
  • 占用大量内存的组件(除非严格控制缓存数量)

7.3 常见问题

  1. 缓存键冲突:确保不同组件使用不同的 key
  2. 内存泄漏:合理设置 max 属性
  3. 状态不一致:注意组件在激活/停用时的状态管理
如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:繁星

本文链接:

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