引言
keep-alive 是 vue 内置的
抽象组件
,可以用于包裹需要缓存的组件。保存组件状态,避免重新渲染,增强性能。
Props
keep-alive 组件接收三个参数,分别为 include
、exclude
、max
- include - 数组、字符串或正则表达式。只有名称匹配的组件会被缓存。
- exclude - 数组、字符串或正则表达式。任何名称匹配的组件都不会被缓存。
- max - 数字。最多可以缓存多少组件实例。
使用
结合动态组件使用
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配。
结合 vue-router 使用
<keep-alive>
<router-view>
<!-- 所有路径匹配到的视图组件都会被缓存! -->
</router-view>
</keep-alive>
缓存指定路由对应组件
<keep-alive>
<router-view v-if="$route.meta.keepAlive" />
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
// vue-router routes 配置
export default [
{
path: '/',
name: 'componentsName',
component: componentsName,
meta: {
keepAlive: true // 需要被缓存
}
}
]
<keep-alive> 是用在其一个直属的子组件被开关的情形。如果你在其中有 v-for 则不会工作。如果有上述的多个条件性的子元素,<keep-alive> 要求同时只有一个子元素被渲染。
深入源码
export default {
name: 'keep-alive',
abstract: true, // 抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。
// 具体关于抽象组件不会渲染出DOM,在 src/core/instance/lifecycle.js
props: {
include: patternTypes, // [String, RegExp, Array]
exclude: patternTypes,
max: [String, Number]
},
created () {
this.cache = Object.create(null) // 新建缓存对象 { key: vnode }
this.keys = [] // 按序保存组件的 key
},
destroyed () {
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
/**
* 监听 include、exclude 动态变化
*/
mounted () {
this.$watch('include', val => {
pruneCache(this, name => matches(val, name)) // function filter (name) { return matches(val, name); }
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
render () {
const slot = this.$slots.default // 获取插槽默认内容
const vnode: VNode = getFirstComponentChild(slot) // 获取第一个子节点
// <keep-alive> 是用在其一个直属的子组件被开关的情形。如果你在其中有 v-for 则不会工作。如果有上述的多个条件性的子元素,<keep-alive> 要求同时只有一个子元素被渲染。
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// check pattern
const name: ?string = getComponentName(componentOptions)
const { include, exclude } = this
/**
* 未匹配到相应组件,不缓存
*/
if (
(include && (!name || !matches(include, name))) ||
(exclude && name && matches(exclude, name))
) {
return vnode
}
const { cache, keys } = this
// 生成 key
const key: ?string = vnode.key == null
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (cache[key]) {
// 命中缓存
vnode.componentInstance = cache[key].componentInstance
// make current key freshest 刷新 keys 数组
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 组件创建时,新建 catch 缓存节点,keys 按序
保存key。通过传入的 include、exclude判断是否命中缓存,命中,则从缓存中拿vnode组件实例,调整key的顺序。未命中,则添加缓存。判断是否超出最大缓存数量,超出,则删除最久未被使用的节点,即keys第一个 key 对应的 vnode
生命周期
组件一旦被 缓存,再次渲染就不会执行 created、mounted生命周期。因此 vue 提供了 activated和 deactivated 两个生命周期函数,在缓存组件再次渲染时执行一些操作。
总结
<keep-alive>
组件通过插槽,获取第一个子节点。根据 include、exclude判断是否需要缓存,通过组件的 key,判断是否命中缓存。利用LRU算法,更新缓存以及对应的 keys 数组。根据 max控制缓存的最大组件数量。