<keep-alive>
是Vue中实现的一个组件。
文件源码:src/core/components/keep-alive.js
export default {
name: 'keep-alive,
abstract: true, // initLifecycle中
props: {
include: patternTypes, // 只有匹配的才会被缓存
exclude: patternTypes, // 任何匹配的都不会缓存
max: [String, Number] // 指定缓存大小
},
created () {
// 缓存已经创建过的VNode
this.cache = Object.create(null)
this.keys = []
},
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) // 获取第一个子节点
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// check pattern
const name: ?string = getComponentName(componentOptions)
const { include, exclude } = this
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
// 如果命中缓存,则直接从缓存中拿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
// 如果配置了max并且缓存长度超过this.max,从缓存中删除第一个
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
// 针对数组、字符串、正则分别匹配
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
}
watch: {
include (val: string | RegExp | Array<string>) {
pruneCache(this, name => matches(val, name))
},
exclude (val: string | RegExp | Array<string>) {
pruneCache(this, name => !matches(val, name))
}
}
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)
}
}
}
}
对cache做遍历,发现缓存的节点名称和新的规则没有匹配上的时候,把这个缓存节点从缓存中摘除。
在渲染过程中,patch之前会进行diff,会先执行prepatch钩子函数,<keep-alive>
组件在执行prepatch时,需要对自己的slots重新解析。并触发<keep-alive>
组件实例 $forceUpdate 逻辑,也就是重新执行 <keep-alive>
的 render 方法,这个时候如果它包裹的第一个组件 vnode 命中缓存,则直接返回缓存中的 vnode.componentInstance,接着又会执行 patch 过程,再次执行到 createComponent 方法。
在执行 init 钩子函数的时候不会再执行组件的 mount 过程了,被 <keep-alive>
包裹的组件在有缓存的时候不会再执行组件的 created、mounted 等钩子函数