在前面的实现中,使用一个Map对象来实现对组件的缓存:
const cache = new Map()
该Map对象的键也就是vnode.type的值,值是用于描述组件的vnode对象
当前KeepAlive组件缓存的处理逻辑可以总结为:
- 如果缓存存在,则继承组件实例,将keptAlive改为true
- 如果缓存不存在,则设置缓存
这里的问题在于,当缓存不存在的时候,总是会设置新的缓存。这会导致缓存不断增加,极端情况下会占用大量内存。
为了解决这个问题,必须设置一个缓存阈值,当缓存数量超过指定阈值时对缓存进行修剪。
Vue.js当前采用的修剪策略叫“最新一次访问”。
首先要为缓存设置最大容器,也就是通过KeepAlive组件的max属性来设置,例如:
<KeepAlive :max="2">
<component :is="dynamicComp" />
</KeepAlive>
在上面这段代码中,设置缓存的容量为2,假设有三个组件Comp1,Comp2,Comp3,并且他们都会被缓存。然后,开始模拟组件切换过程中缓存的变化,如下所示:
- 初始渲染Comp1并缓存它,此时缓存队列是[Comp1],并且最新一次访问(或渲染)的组件时Comp1
- 切换到Comp2并缓存它,此时缓存队列是[Comp1,Comp2],并且最新一次访问(或渲染)的组件时Comp2
- 切换到 Comp3,此时缓存容量已满,需要修剪,而当前最新一次访问(或渲染)的组件是Comp2,所以该被修剪的是Comp1,当缓存修剪完毕后,将会出现空余的缓存空间用来存储Comp3,所以,现在的缓存队列是[Comp2,Comp3],并且最新一次渲染的组件变成了Comp3
还可以换一种切换组件的方式,如下所示
- 初始渲染Comp1并缓存它,此时缓存队列是[Comp1],并且最新一次访问(或渲染)的组件时Comp1
- 切换到Comp2并缓存它,此时缓存队列是[Comp1,Comp2],并且最新一次访问(或渲染)的组件时Comp2
- 再次换会Comp1,由于Comp1已经在缓存队列中,所以不需要修剪缓存,只需要激活组件即可,但要将最新一次渲染的组件设置为Comp1
- 切换到 Comp3,此时缓存容量已满,需要修剪,由于Comp1是最新一次被渲染的,所以它是“安全”的,即不会被修剪掉,所以最终会被修剪掉的是Comp2。于是,现在的缓存队列是:[Comp1,Comp3],并且最新一次渲染的组件变成了Comp3
可以看到,在不同的模拟策略下,最终的缓存结果会有所不同。
“最新一次访问”的缓存修剪策略的核心在于,需要把当前访问(或渲染)的组件作为最新一次渲染的组件,并且该组件在缓存修剪过程中始终是安全的,即不会被修剪。
而且在用户接口层面,允许用户实现自定义的缓存策略,这体现在KeepAlive组件新增了cache接口,允许用户执行缓存实例:
<KeepAlive :cache="cache">
<Comp />
</KeepAlive>
缓存示例需要满足固定的格式,一个基本的缓存示例的实现如下:
// 自定义实现
const _cache = new Map()
const cache: KeepAliveCache = {
get(key){
_cache.get(key)
},
set(key,value){
_cache.set(key,value)
},
delete(key){
_cache.delete(key)
},
forEach(fn){
_cache.forEach(fn)
}
}
在KeepAlive组件的内部实现中,如果用户提供了自定义的缓存实例,则直接使用该缓存实例来管理缓存。从本质上来说,这等价于将缓存的管理权限从KeepAlive组件转交给用户了。