keep-alive 执行的生命周期
当引入keep-alive的时候,页面第一次进入,钩子的触发顺序created-> mounted-> activated,退出时触发deactivated。当再次进入(前进或者后退)时,只触发activated。
<router-view v-if="$route.meta.keepAlive" style="min-height:100%"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive" style="min-height:100%"></router-view>
//不需要刷新的路由配置里面配置 meta: {keepAlive: true}, 这个路由则显示在上面标签;
//需要刷新的路由配置里面配置 meta: {keepAlive: false}, 这个路由则显示在下面标签;
props包含哪三个
-
include包含的组件(可以为字符串,数组,以及正则表达式,只有匹配的组件会被缓存) -
exclude排除的组件(以为字符串,数组,以及正则表达式,任何匹配的组件都不会被缓存) -
max缓存组件的最大值(类型为字符或者数字,可以控制缓存组件的个数),最大为10
LRU算法(缓存淘汰算法)
props中的max存在,keep-alive引入了与之搭配的LRU算法
该算法秉承的原则即:如果当前数据被访问过,接下来被访问的几率就会加大。
- 新数据插入到链表尾部;
- 每当缓存命中(即缓存数据被访问),则将数据移到链表尾部
- 当链表满的时候,将链表头部的数据丢弃。
延伸知识:abstract的作用、拥有该属性的标签有哪些、object.create(null)创建了一个什么对象、vue2中$slots.default获取插槽的默认元素
源码中几个函数的具体用途
1.初始化
export default {
name: 'keep-alive',
abstract: true,
//在组件开头就设置 `abstract` 为 `true`,代表该组件已经被提取出来,不会在页面生成,其子组件会直接挂载到该组件的父组件上。
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
created () {
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
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
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
```匹配条件通过会进入缓存机制的逻辑,如果命中缓存,从 `cache` 中获取缓存的实例设置到当前的组件上,并调整 `key` 的位置将其放到最后。如果没命中缓存,将当前 `VNode` 缓存起来,并加入当前组件的 `key`。如果缓存组件的数量超出 `max` 的值,即缓存空间不足,则调用 `pruneCacheEntry` 将最旧的组件从缓存中删除,即 `keys[0]` 的组件。之后将组件的 `keepAlive` 标记为 `true`,表示它是被缓存的组件。
```// 遍历缓存表
function pruneCache(filter?: (name: string) => boolean) {
cache.forEach((vnode, key) => {
const name = getComponentName(vnode.type as ConcreteComponent);
if (name && (!filter || !filter(name))) {
// !filter(name) 即 name 在 includes 或不在 excludes 中
pruneCacheEntry(key);
}
});
}
// 依据 key 值从缓存表中移除对应组件
function pruneCacheEntry(key: CacheKey) {
const cached = cache.get(key) as VNode;
if (!current || cached.type !== current.type) {
/* 当前没有处在 activated 状态的组件
* 或者当前处在 activated 组件不是要删除的 key 时
* 卸载这个组件
*/
unmount(cached); // unmount方法里同样包含了 resetShapeFlag
} else if (current) {
// 当前组件在未来应该不再被 keepAlive 缓存
// 虽然仍在 keepAlive 的容量中但是需要刷新当前组件的优先级
resetShapeFlag(current);
// resetShapeFlag
}
cache.delete(key);
keys.delete(key);
}
function resetShapeFlag(vnode: VNode) {
let shapeFlag = vnode.shapeFlag; // shapeFlag 是 VNode 的标识
// ... 清除组件的 shapeFlag
}
实现代码展示
1.route字段中添加keepalive,true即为缓存路由,否则不缓存
const route = {
path: menuList[i].url,
component:
routeAllPathToCompMap[`../views/${menuList[i].url}/index.vue`],
name: menuList[i].url,
meta: {
title: menuList[i].name,
menuId: menuList[i].menuId,
keepAlive:true
}
}
2.在页面使用keepalive
<RouterView v-slot="{ Component }">
<KeepAlive :include="keepAliveComponents">
<component :is="Component" :key="route.fullPath" />
</KeepAlive>
</RouterView>
keepalive实现的逻辑梳理
-
使用 LRU 缓存机制进行缓存,max 限制缓存表的最大容量
-
根据设定的 include/exclude(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例
-
根据组件 ID 和 tag 生成缓存 Key ,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该 key 在 this.keys 中的位置(更新 key 的位置是实现 LRU 置换策略的关键)
-
获取节点名称,或者根据节点 cid 等信息拼出当前 组件名称 ( 因此给各个组件标记各自名称这件事变得尤为重要 )
-
获取 keep-alive 包裹着的第一个子组件对象及其组件名