Vue3源码解析--keep-alive

<keep-alive> 是 Vue 3 中一个非常重要的内置组件,用于缓存动态组件的状态,避免重复渲染和销毁。它通常用于优化性能,尤其是在需要频繁切换组件的场景(如标签页、路由切换等)。本文将从源码层面深入解析 <keep-alive> 的实现原理。


1. <keep-alive> 的基本用法

在 Vue 3 中,<keep-alive> 的使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>
</template>

<script>
export default {
data() {
return {
currentComponent: 'ComponentA',
};
},
};
</script>
  • <keep-alive> 包裹的动态组件会被缓存,当组件切换时,不会触发组件的销毁和重新创建。
  • 缓存的组件会保留其状态(如数据、DOM 状态等)。

2. <keep-alive> 的核心功能

<keep-alive> 的核心功能包括:

  1. 缓存组件实例:将动态组件的实例缓存起来,避免重复渲染。
  2. 生命周期钩子:提供 activateddeactivated 钩子,用于监听组件的激活和停用。
  3. LRU 缓存策略:通过 LRU(最近最少使用)算法管理缓存,防止内存泄漏。

3. 源码解析

Vue 3 的 <keep-alive> 源码位于 packages/runtime-core/src/components/KeepAlive.ts。以下是其核心实现逻辑的解析。

3.1 组件定义

<keep-alive> 是一个函数式组件,其定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export const KeepAliveImpl: ComponentOptions = {
name: `KeepAlive`,
__isKeepAlive: true,

props: {
include: [String, RegExp, Array],
exclude: [String, RegExp, Array],
max: [String, Number],
},

setup(props: KeepAliveProps, { slots }: SetupContext) {
// 缓存相关的逻辑
},
};
  • includeexclude:用于控制哪些组件需要缓存。
  • max:缓存的最大数量,超过时会使用 LRU 算法淘汰最久未使用的组件。

3.2 缓存管理

<keep-alive> 使用一个 Map 对象来缓存组件实例:

1
2
const cache: Cache = new Map();
const keys: Keys = new Set();
  • cache:用于存储组件实例。
  • keys:用于记录缓存组件的 key,用于实现 LRU 算法。

3.2.1 缓存组件

当组件被渲染时,<keep-alive> 会将其实例缓存起来:

1
2
3
4
5
function cacheVNode(vnode: VNode) {
const key = vnode.key == null ? comp : vnode.key;
cache.set(key, vnode);
keys.add(key);
}
  • vnode.key:组件的唯一标识,用于区分不同的组件实例。
  • 如果缓存数量超过 max,会淘汰最久未使用的组件。

3.2.2 淘汰策略

当缓存数量超过 max 时,<keep-alive> 会使用 LRU 算法淘汰最久未使用的组件:

1
2
3
4
5
6
7
8
function pruneCacheEntry(key: CacheKey) {
const cached = cache.get(key);
if (cached) {
unmount(cached);
cache.delete(key);
keys.delete(key);
}
}
  • unmount:卸载组件实例,触发其生命周期钩子deactivated。

3.3 生命周期钩子

<keep-alive> 通过劫持组件的生命周期钩子,实现了 activateddeactivated 钩子。

3.3.1 激活钩子(activated)

当缓存的组件被重新激活时,会触发 activated 钩子:

1
2
3
4
5
function activate(vnode: VNode) {
if (vnode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
vnode.component!.activated();
}
}
  • vnode.component:组件的实例。
  • activated:组件的激活钩子。

3.3.2 停用钩子(deactivated)

当缓存的组件被停用时,会触发 deactivated钩子:

1
2
3
4
5
function deactivate(vnode: VNode) {
if (vnode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
vnode.component!.deactivated();
}
}
  • deactivated:组件的停用钩子。

3.4 渲染逻辑

<keep-alive> 的渲染逻辑主要通过 render 函数实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function render() {
const slot = slots.default();
const vnode = slot[0];

if (vnode && vnode.shapeFlag & ShapeFlags.COMPONENT) {
const key = vnode.key == null ? comp : vnode.key;

if (cache.has(key)) {
// 从缓存中获取组件实例
vnode.component = cache.get(key).component;
vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;
} else {
// 缓存新的组件实例
cacheVNode(vnode);
}

vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;
}

return vnode;
}
  • **slots.default()**:获取默认插槽的内容。
  • vnode.shapeFlag:用于标记组件的类型和状态。
  • 如果组件已经缓存,则直接从缓存中获取实例;否则,缓存新的组件实例。

4. 总结

<keep-alive> 的实现原理可以总结为以下几点:

  1. 缓存管理:通过 Map 对象缓存组件实例,并使用 LRU 算法管理缓存。
  2. 生命周期钩子:通过劫持组件的生命周期钩子,实现 activated 和 deactivated 钩子。
  3. 渲染逻辑:在渲染时,优先从缓存中获取组件实例,避免重复渲染。