概述
在Vue里面,keep-alive 是一个内置的抽象组件,它的作用是缓存所包裹组件的实例。
源码分析
源码地址:src/core/components/keep-alive.js
官方参数解释
Props:
include- 字符串或正则表达式。只有名称匹配的组件会被缓存。exclude- 字符串或正则表达式。任何名称匹配的组件都不会被缓存。max- 数字。最多可以缓存多少组件实例。
<keep-alive :include="cachedViews">
<router-view :key="key" />
</keep-alive>
从生命周期开始
既然 keep-alive 是一个组件,那么就会有组件的生命周期,接下来就根据这个顺序来讲述。
created () {
this.cache = Object.create(null)
this.keys = []
}
首先在创建完组件实例的时候会初始化两个参数,keys 存放组件实例对应的key,而 cache 则是相当于一个缓存池(代码层面就是一个对象),后面部分讲述的会更容易理解。
缓存方法
cacheVNode() {
const { cache, keys, vnodeToCache, keyToCache } = this
if (vnodeToCache) {
const { tag, componentInstance, componentOptions } = vnodeToCache
cache[keyToCache] = {
name: getComponentName(componentOptions),
tag,
componentInstance,
}
keys.push(keyToCache)
// prune oldest entry
// 删除掉最少用的,给新的腾出空间
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
this.vnodeToCache = null
}
}
mounted
初始化后,可以看到 mounted 里添加了 watch 监听两个参数 include 和 exclude ,这是为了匹配参数然后删除不需要的缓存。如下:
mounted () {
this.cacheVNode()
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
}
匹配的方式比较简单,主要通过三种方式去匹配组件名称,分别为字符串、正则表达式,字符串数组。代码如下:
/* 通过正则匹配组件名称 */
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 entry: ?CacheEntry = cache[key]
if (entry) {
const name: ?string = entry.name
// filter 和 matches相呼应
if (name && !filter(name)) {
pruneCacheEntry(cache, key, keys, _vnode)
}
}
}
}
/** 删除具体的记录,并销毁对象 */
function pruneCacheEntry (
cache: CacheEntryMap,
key: string,
keys: Array<string>,
current?: VNode
) {
const entry: ?CacheEntry = cache[key]
if (entry && (!current || entry.tag !== current.tag)) {
entry.componentInstance.$destroy()
}
cache[key] = null
remove(keys, key)
}
以上可以看出 pruneCache 方法主要是遍历缓存池,匹配上的再调用 pruneCacheEntry 方法,实际上的删除操作在后者,这里需要注意的是删除缓存前需要把该组件实例销毁 cached.componentInstance.$destroy()
render
核心的逻辑处理在 render 方法里
render () {
// 插槽
const slot = this.$slots.default
// 获取第一个组件vnode对象
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
// 判断当前key是否存在缓存中,如果存在则更新数组,这里用到了LRU算法
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
remove(keys, key)
keys.push(key)
} else {
// delay setting the cache until update
// 延迟到下次更新时使用
this.vnodeToCache = vnode
this.keyToCache = key
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
简单梳理下逻辑:
- 第一步,找到包裹的组件虚拟dom节点vnode。
- 第二步,判断是否需要缓存,如果不需要则直接返回。
- 第三步,需要缓存的情况,更新缓存池。如果缓存池已存在该vnode就直接复用,然后把
key从keys里删掉,再把keypush进keys(其实就是使用了LRU算法),如果不存在,则把这vnode和key留着下次用。
总结
- keep-alive 是个抽象组件,所以也有生命周期
- keep-alive 是通过
keys和cache两个变量来管理缓存的,可以把keys看成版本号管理,cache则是组件缓存池 - 如果与
key对应的缓存已经存在则直接复用,再更新一把keys