keep-alive原理

keep-alive介绍

它是一个vue内置抽象组件,不会渲染成Dom,也不会出现在父组件链中.当它包裹动态组件时将缓存不活动的组件实例,而不是销毁它们.

组件的销毁会触发destroy生命周期,v-if切换或路由跳转都会销毁当前组件,子组件在父组件中被keepalive时,父组件不销毁时是能够被缓存的,但父组件被销毁,子组件也将销毁.

keep alive能保持组件状态,避免重复渲染,提高性能.

keep alive使用

它接受三个参数:

  • include: 包含哪些被缓存
  • exclude: 哪些将不被缓存
  • max: 缓存最大数量,新的缓存加入,达到最大缓存数量时,将最久未访问的老缓存清除.

上述max中清除缓存用的是LRU策略置换缓存数据.对于在内存中但又不用的数据块(内存块)叫做LRU,操作系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据。


源码分析

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
export default {
name: 'keep-alive',
abstract: true, // 判断当前组件虚拟dom是否渲染成真实dom的关键

props: { // 三个参数
include: [String, RegExp, Array],
exclude: [String, RegExp, Array],
max: [String, Number]
},

created () {
this.cache = Object.create(null) // 用来缓存组件的对象(缓存的是虚拟dom)
this.keys = [] // 缓存的组件的键
/*
this.cache = {
'key1':'组件1',
'key2':'组件2',
// ...
}
this.keys = [key1, key2, ...]
*/
},

destroyed () { // keep alive组件被销毁时调用
for (const key in this.cache) { // 遍历所有被缓存的组件
pruneCacheEntry(this.cache, key, this.keys) // 删除除了当前被渲染的组件外的所有组件缓存
/*
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 // 删除对应cache对象
remove(keys, key) // 删除对应键
}
*/
}
},

mounted () {
// 监听两个参数变化,发生变化时删除对应需要删除的缓存
this.$watch('include', val => { // 监听include变化
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => { // 监听exclude变化
pruneCache(this, name => !matches(val, name))
})
/*
function pruneCache (keepAliveInstance, filter) {
const { cache, keys, _vnode } = keepAliveInstance // keep alive实例
for (const key in cache) { // 遍历缓存组件
const cachedNode = cache[key]
if (cachedNode) {
const name = getComponentName(cachedNode.componentOptions)
if (name && !filter(name)) { // 过滤出需要删除的缓存组件
pruneCacheEntry(cache, key, keys, _vnode) // 删除
}
}
}
}
*/
},

render() {
/* 获取默认插槽中的第一个组件节点 */
const slot = this.$slots.default // keep alive只渲染第一个*组件*节点
/*
<keep-alive> 只渲染第一个组件,即组件一;当组件一销毁后会会渲染111,再展示组件一时,111消失
<p>111</p>
<Comp v-if="flag">组件一</Comp>
<Comp v-if="flag">组件二</Comp>
<p>222</p>
</keep-alive>
*/
const vnode = getFirstComponentChild(slot) // 当前组件虚拟dom
/* 获取该组件节点的componentOptions */
const componentOptions = vnode && vnode.componentOptions // 当前组件节点配置项,包括name,tag等

if (componentOptions) {
/* 获取该组件节点的名称,优先获取组件的name字段,如果name不存在则获取组件的tag */
const name = getComponentName(componentOptions)

const { include, exclude } = this
/* 如果name不在inlcude中或者存在于exlude中则表示不缓存,直接返回vnode */
if (
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode //
}

const { cache, keys } = this
const key = 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])
}
}

参考:

keep-alive实现原理
通俗易懂了解Vue内置组件keep-alive内部原理