一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第28天,点击查看活动详情。
keep-alive
作用
keep-alive作为一种vue的内置组件,主要作用是缓存组件状态。当需要组件的切换时,不用重新渲染组件,避免多次渲染,就可以使用keep-alive包裹组件。
props
include
字符串或者正则表达式,只有名称匹配的组件会被缓存exclude
字符串或者正则表达式,任何名臣匹配的组件都不会被缓存max
数字,最多可以缓多少组件实例
用法
keep-alive
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和transition
相似,keep-alive
是一个抽象组件:他自身不会渲染一个dom元素,也不会出现在组件的父组件链中。
当组件在keep-alive
内被切换,它的activated
和deactivated
这两个生命周期钩子函数将会被对应执行。
主要用于保留组件状态或者避免重新渲染
<!-- 基本 -->
<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>
原理
keep-alive
是一个组件,这个组件中有三个属性,分别是include
、exclude
、max
,在created
中创建缓存列表和缓存组件的key列表,销毁的时候会做一个循环销毁清空所有的缓存和key。当mounted
时会监控include
和include
属性,进行组件的缓存处理。
如果发生变化会动态的添加和删除缓存,渲染的时候会去拿默认插槽
,只缓存第一个组件,根据组件的名字判断是否在缓存中,如果在就缓存,不在就return掉,不在就直接return掉。缓存的时候,如果组件没有key,就自己通过组件的标签,key和cid拼接一个key。
该组件如果缓存过,就直接拿到组件实例,如果没有就存进当前的vnode
中,和key做一个对应关系。这里面有一个算法叫LRU
,如果有key就不停的取,如果超限了就采用LRU
进行删除最近最久未使用的,从前面删除,LRU
就是将当前使用的往数组的后面移,在最前面的就是最久未使用的。
源码
文件位置:src/core/components/keep-alive.js
export default {
name: 'keep-alive',
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
created () {
this.cache = Object.create(null) // 创建缓存列表
this.keys = [] // 创建缓存组件的key列表
},
destroyed () {
for (const key in this.cache) {// keep-alive销毁时,循环清空所有的缓存和key
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted () {// 会监控include 和 include属性 进行组件的缓存处理
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
// 如果组件没key 就自己通过 组件的标签和key和cid 拼接一个key
if (cache[key]) { // 如果缓存中有key
vnode.componentInstance = cache[key].componentInstance // 直接拿到组件实例
// make current key freshest
remove(keys, key) // 删除当前的key // LRU 最近最久未使用法
keys.push(key) // 并将key放到缓存的最后面
} else {
cache[key] = vnode // 缓存vnode
keys.push(key) // 将key 存入
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) { // 缓存的太多超过了max就需要删除掉
pruneCacheEntry(cache, keys[0], keys, this._vnode) // 要删除第0个 但是现在渲染的就是第0个
}
}
vnode.data.keepAlive = true // 并且标准keep-alive下的组件是一个缓存组件
}
return vnode || (slot && slot[0]) // 返回当前的虚拟节点
}