某人

此前素未谋面、此后遥遥无期

0%

vue之keepAlive

keep-alive语法

props
  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
  • max - 数字。最多可以缓存多少组件实例。

includeexclude prop 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:

用法
  • <keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 <transition> 相似
  • <keep-alive> 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。
  • 当组件在 <keep-alive> 内被切换,它的 activateddeactivated 这两个生命周期钩子函数将会被对应执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 基本 -->
<keep-alive>
<component :is="view"></component>
</keep-alive>

<!-- 多个条件判断的子组件 -->
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>

<!-- 和 `<transition>` 一起使用 -->
<transition>
<keep-alive>
<component :is="view"></component>
</keep-alive>
</transition>

匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配。

不会在函数式组件中正常工作,因为它们没有缓存实例。

源码

get-first-component-child.js
1
2
3
4
5
6
7
8
9
10
11
12
13
import { isDef } from 'shared/util'
import { isAsyncPlaceholder } from './is-async-placeholder'
// 获取第一个子组件(d带componentOptions)
export function getFirstComponentChild (children: ?Array<VNode>): ?VNode {
if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) {
const c = children[i]
if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
return c
}
}
}
}
v2.6.12/src/core/components/keep-alive.js
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// 一般都在代码档案的最上面一行加入类型检查的注释,没加Flow工具是不会进行检查的
/* @flow */

import { isRegExp, remove } from 'shared/util'
// 获取第一个子组件
import { getFirstComponentChild } from 'core/vdom/helpers/index'

// ?VNode代表的是类型可自定义,也就是值的部份除了定义的类型,也可以是null或undefined,不过这属性是需要的
type VNodeCache = { [key: string]: ?VNode };

// 获取组件名/标签名
// 匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)
function getComponentName (opts: ?VNodeComponentOptions): ?string {
return opts && (opts.Ctor.options.name || opts.tag)
}

// 是否匹配
// pattern可以用逗号分隔字符串、正则表达式或一个数组
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) {
// 当前keepAlive示例 _vnode 已渲染的虚拟dom
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)
}
}
}
}

// 移除或销毁缓存的组件
// current属性是可选的
function pruneCacheEntry (
cache: VNodeCache,
key: string,
keys: Array<string>,
current?: VNode
) {
const cached = cache[key]
if (cached && (!current || cached.tag !== current.tag)) {
// 执行组件的destory钩子函数 销毁组件
cached.componentInstance.$destroy()
}
// 清空缓存的虚拟dom
cache[key] = null
// 从数组中移除该项key
remove(keys, key)
}

// patternTypes 数组
// 类型 可以用逗号分隔字符串、正则表达式或一个数组
const patternTypes: Array<Function> = [String, RegExp, Array]

export default {
name: 'keep-alive',
//抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。
abstract: true,

props: {
// 字符串或正则表达式。只有名称匹配的组件会被缓存。
include: patternTypes,
// 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
exclude: patternTypes,
// 最多可以缓存多少组件实例。
max: [String, Number]
},

created () {
// 缓存虚拟dom
this.cache = Object.create(null)
// 缓存的虚拟dom的健集合
this.keys = []
},

destroyed () {
// 删除所有的缓存组件
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},

mounted () {
// 对include和exclude参数进行监听,然后实时地更新(删除)
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},

render () {
// 获取默认分发的虚拟dom(可能多个)
const slot = this.$slots.default
//获取第一个子组件的虚拟dom
const vnode: VNode = getFirstComponentChild(slot)
// 获取虚拟dom组件参数
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions

if (componentOptions) {
// ?string代表的是类型可自定义string,也就是值的部份除了定义的类型,
// 也可以是null或undefined,不过这属性是需要的
//获取到组件的name,存在组件名则直接使用组件名,否则会使用标签(tag)
const name: ?string = getComponentName(componentOptions)
const { include, exclude } = this
// include 只有名称匹配的组件会被缓存
// exclude 匹配的组件都不会被缓存
if (
// 不包含
(include && (!name || !matches(include, name))) ||
// 排除在外
(exclude && name && matches(exclude, name))
) {
return vnode
}

const { cache, keys } = this
// 获取/生成代表组件的唯一标识key
const key: ?string = vnode.key == null
// 同一构造函数可能会注册为不同的本地组件
// 因此仅凭cid是不够的(#3269)
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key

if (cache[key]) {
// 已缓存虚拟dom
// 将缓存的vnode的componentInstance(组件实例)赋值到目前的vnode上面
vnode.componentInstance = cache[key].componentInstance
// 移除缓存的key
remove(keys, key)
// 重新插入新的key,相当于修改了key的顺序
keys.push(key)
} else {
// 未缓存虚拟dom,则进行缓存虚拟dom
cache[key] = vnode
//缓存key
keys.push(key)
// 如果缓存的组件数量大于max时,销毁最早缓存的那个组件
if (this.max && keys.length > parseInt(this.max)) {
//_vnode 是经过 render处理过的
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
//标记已缓存
vnode.data.keepAlive = true
}
// keepalive组件只能渲染一个子元素
return vnode || (slot && slot[0])
}
}