知食记
搜索文档…
keep-alive

keep-alive介绍

keep-alive是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中;使用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。

用法

    在动态组件中的应用
1
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
2
<component :is="currentComponent"></component>
3
</keep-alive>
Copied!
    在vue-router中的应用
1
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
2
<router-view></router-view>
3
</keep-alive>
Copied!
include定义缓存白名单,keep-alive会缓存命中的组件;exclude定义缓存黑名单,被命中的组件将不会被缓存;max定义缓存组件上限,超出上限使用LRU的策略置换缓存数据。

实现

1
// src/core/components/keep-alive.js
2
export default {
3
name: 'keep-alive',
4
abstract: true, // 判断当前组件虚拟dom是否渲染成真是dom的关键
5
6
props: {
7
include: patternTypes, // 缓存白名单
8
exclude: patternTypes, // 缓存黑名单
9
max: [String, Number] // 缓存的组件实例数量上限
10
},
11
12
created () {
13
this.cache = Object.create(null) // 缓存虚拟dom
14
this.keys = [] // 缓存的虚拟dom的健集合
15
},
16
17
destroyed () {
18
for (const key in this.cache) { // 删除所有的缓存
19
pruneCacheEntry(this.cache, key, this.keys)
20
}
21
},
22
23
mounted () {
24
// 实时监听黑白名单的变动
25
this.$watch('include', val => {
26
pruneCache(this, name => matches(val, name))
27
})
28
this.$watch('exclude', val => {
29
pruneCache(this, name => !matches(val, name))
30
})
31
},
32
33
render () {
34
// 先省略...
35
}
36
}
37
Copied!
如上所示定义了一个abstract: true 的组件
keep-alive在它生命周期内定义了三个钩子函数:
    created
    初始化两个对象分别缓存VNode(虚拟DOM)和VNode对应的键集合
    destroyed
    删除this.cache中缓存的VNode实例。我们留意到,这里不是简单地将this.cache置为null,而是遍历调用pruneCacheEntry函数删除。pruneCacheEntry会调用内部的$destroy钩子函数
    mounted
    mounted这个钩子中对includeexclude参数进行监听,然后实时地更新(删除)this.cache对象数据。pruneCache函数的核心也是去调用pruneCacheEntry
1
// src/core/components/keep-alive.js
2
render () {
3
const slot = this.$slots.default
4
const vnode: VNode = getFirstComponentChild(slot) // 找到第一个子组件对象
5
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
6
if (componentOptions) { // 存在组件参数
7
// check pattern
8
const name: ?string = getComponentName(componentOptions) // 组件名
9
const { include, exclude } = this
10
if ( // 条件匹配
11
// not included
12
(include && (!name || !matches(include, name))) ||
13
// excluded
14
(exclude && name && matches(exclude, name))
15
) {
16
return vnode
17
}
18
19
const { cache, keys } = this
20
const key: ?string = vnode.key == null // 定义组件的缓存key
21
// same constructor may get registered as different local components
22
// so cid alone is not enough (#3269)
23
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
24
: vnode.key
25
if (cache[key]) { // 已经缓存过该组件
26
vnode.componentInstance = cache[key].componentInstance
27
// make current key freshest
28
remove(keys, key)
29
keys.push(key) // 调整key排序
30
} else {
31
cache[key] = vnode // 缓存组件对象
32
keys.push(key)
33
// prune oldest entry
34
if (this.max && keys.length > parseInt(this.max)) { // 超过缓存数限制,将第一个删除
35
pruneCacheEntry(cache, keys[0], keys, this._vnode)
36
}
37
}
38
39
vnode.data.keepAlive = true // 渲染和执行被包裹组件的钩子函数需要用到
40
}
41
return vnode || (slot && slot[0])
42
}
Copied!
第一步:获取keep-alive包裹着的第一个子组件对象及其组件名;
第二步:根据设定的黑白名单(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例(VNode),否则执行第三步;
第三步:根据组件ID和tag生成缓存Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该keythis.keys中的位置(更新key的位置是实现LRU置换策略的关键),否则执行第四步;
第四步:在this.cache对象中存储该组件实例并保存key值,之后检查缓存的实例数量是否超过max的设置值,超过则根据LRU置换策略删除最近最久未使用的实例(即是下标为0的那个key)。
第五步:最后并且很重要,将该组件实例的keepAlive属性值设置为true

区别与普通组件的控制

不会生成真正的dom节点

keep-alive不会生成真正的dom节点。这是Vue在初始化声明周期时,为组件实例建立父子关系会根据abstract属性决定是否忽略某个组件。

只执行一次的钩子

1
// src/core/vdom/create-component.js
2
const componentVNodeHooks = {
3
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
4
if (
5
vnode.componentInstance &&
6
!vnode.componentInstance._isDestroyed &&
7
vnode.data.keepAlive
8
) {
9
// kept-alive components, treat as a patch
10
const mountedNode: any = vnode // work around flow
11
componentVNodeHooks.prepatch(mountedNode, mountedNode)
12
} else {
13
const child = vnode.componentInstance = createComponentInstanceForVnode(
14
vnode,
15
activeInstance
16
)
17
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
18
}
19
}
20
// ...
21
}
Copied!
vnode.componentInstancekeepAlive同时为truly值时,不再进入$mount过程,那mounted之前的所有钩子函数(beforeCreatecreatedmounted)都不再执行。

可重复的activated

patch的阶段,最后会执行invokeInsertHook函数,而这个函数就是去调用组件实例(VNode)自身的insert钩子
最近更新 1yr ago