keep-alive 的实现原理和缓存策略

keep-alive的源码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//src/core/components/keep-alive.js
import { isRegExp, remove } from "shared/util";
import { getFirstComponentChild } from "core/vdom/helpers/index";

type VNodeCache = { [key: string]: ?VNode };

function getComponentName(opts: ?VNodeComponentOptions): ?string {
return opts && (opts.Ctor.options.name || opts.tag);
}

function matches(
pattern: string | RegExp | Array<string>,
name: string
): boolean {
if (Array.isArray(pattern)) {
return pattern.indexOf(name) > -1;
} else if (typeof pattern === "string") {
return pattern.split(",").indexOf(name) > -1;
} else if (isRegExp(pattern)) {
return pattern.test(name);
}
/* istanbul ignore next */
return false;
}

function pruneCache(keepAliveInstance: any, filter: Function) {
const { cache, keys, _vnode } = keepAliveInstance;
for (const key in cache) {
const cachedNode: ?VNode = cache[key];
if (cachedNode) {
const name: ?string = getComponentName(cachedNode.componentOptions);
if (name && !filter(name)) {
pruneCacheEntry(cache, key, keys, _vnode);
}
}
}
}

function pruneCacheEntry(
cache: VNodeCache,
key: string,
keys: Array<string>,
current?: VNode
) {
const cached = cache[key];
if (cached && (!current || cached.tag !== current.tag)) {
cached.componentInstance.$destroy();
}
cache[key] = null;
remove(keys, key);
}

const patternTypes: Array<Function> = [String, RegExp, Array];

export default {
name: "keep-alive",
abstract: true, //抽象组件属性,参考此链接介绍 https://router.vuejs.org/zh/api/#routes

props: {
include: patternTypes, // 被缓存组件
exclude: patternTypes, // 不被缓存组件
max: [String, Number] // 指定缓存大小
},

created() {
this.cache = Object.create(null);
//Object.create 创建一个对象,并继承传入对象的__proto__
//https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create

this.keys = []; //缓存的key 列表
},

destroyed() {
for (const key in this.cache) {
//清除缓存
pruneCacheEntry(this.cache, key, this.keys);
}
},

mounted() {
//监听缓存的变动
this.$watch("include", val => {
pruneCache(this, name => matches(val, name));
});
//监听不缓存的变动
this.$watch("exclude", val => {
pruneCache(this, name => !matches(val, name));
});
},

render() {
const slot = this.$slots.default;
const vnode: VNode = getFirstComponentChild(slot); // 获取第一个子元素的 vnode
const componentOptions: ?VNodeComponentOptions =
vnode && vnode.componentOptions;
if (componentOptions) {
// check pattern
const name: ?string = getComponentName(componentOptions);
const { include, exclude } = this;

//组件不在inlcude中或者在exlude中 直接返回vnode
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode;
}

const { cache, keys } = this;
const key: ?string =
vnode.key == null
? // same constructor may get registered as different local components
// so cid alone is not enough (#3269)
componentOptions.Ctor.cid +
(componentOptions.tag ? `::${componentOptions.tag}` : "")
: vnode.key;

if (cache[key]) {
//如果缓存存在,拿到缓存的组件,然后把它放在最后一位
vnode.componentInstance = cache[key].componentInstance;
// make current key freshest
remove(keys, key);
keys.push(key);
} else {
//如果缓存不存在,把它放到缓存中
cache[key] = vnode;
keys.push(key);
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode);
}
}

vnode.data.keepAlive = true;
}
return vnode || (slot && slot[0]);
}
};