keep-alive是什么?
<KeepAlive> 是一个内置组件,可以在组件切换时,保存其包裹的组件的状态,使其不被销毁,防止多次渲染。其拥有两个独立的生命周期钩子函数 actived 和 deactived,使用keep-alive包裹的组件在切换时不会被销毁,而是缓存到内存中并执行 deactived 钩子函数,命中缓存渲染后会执行 actived 钩子函数。
当然keep-alive不仅仅是能够保存页面/组件的状态这么简单,它还可以避免组件反复创建和渲染,有效提升系统性能。总的来说,keep-alive用于保存组件的渲染状态,它的功能是在多个组件间动态切换时缓存被移除的组件实例。
keep-alive是一个抽象组件:它自身不会渲染一个DOM元素,也不会出现在父组件链中;使用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们
KeepAlive 功能
KeepAlive 一词借鉴于 HTTP 协议,在 HTTP 协议里面 KeepAlive 又称持久连接,作用是允许多个请求/响应共用同一个 HTTP 连接,解决了频繁的销毁和创建 HTTP 连接带来的额外性能开销。而同理 Vue 里的 KeepAlive 组件也是为了避免一个组件被频繁的销毁/重建,避免了性能上的开销。
// App.vue
<Test :msg="curTab" v-if="curTab === 'Test'"></Test>
<HelloWorld :msg="curTab" v-if="curTab === 'HelloWorld'"></HelloWorld>
<div @click="toggle">toggle</div>
上述代码可以看到,如果我们频繁点击 toggle 时会频繁的渲染 Test/HelloWorld 组件,当用户频繁的点击时 Test 组件需要频繁的销毁/渲染,这就造成很大的渲染性能损失。
所以为了解决这种性能开销,你需要知道是时候使用 KeepAlive 组件
<KeepAlive>
<component :is="curTab === 'Test' ? Test : HelloWorld" :msg="curTab"></component>
</KeepAlive>
<div @click="toggle">toggle</div>
如何实现
其实就是缓存管理和特定的销毁和渲染逻辑,使得它不同于其他组件。
KeepAlive 组件在卸载组件时并不能真的将其卸载,而是将其放到一个隐藏的容器里面,当被激活时再从隐藏的容器中拿出来挂载到真正的 dom 上就行,这也就对应了 KeepAlive 的两个独特的生命周期activated和deactivated
具体实现上,keep-alive在内部维护了一个key数组和一个缓存对象
//keep-alive 内部声明周期函数
created () {
this.cache = Object.create(null)
this.keys = []
},
key数组记录目前缓存的组件key值,如果组件没有指定key值,会自动生成一个唯一的key值
cache对象会以key值为键,vnode为值,用于缓存组件对应的虚拟DOM
在keep-alive的渲染函数中,其基本逻辑是判断当前渲染的vnode是否有对应的缓存,如果有,会从缓存中读取到对应的组件实例,如果没有就会把它缓存。
当缓存的数量超过max设置的数值时,keep-alive会移除key数组中的第一个元素
render () {
const slot = this.$slots.default; //获取默认插槽
const vnode = getFirstComponentChild(slot); //得到插槽中第一个组件的vnode
const name = getComponentName(vnode.componentOptions); //获取组件名字
const { cache, keys } = this; //获取当前的混村内对象和key数组
const key: ?string = vnode.key == null
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key; //获取组件的key值,如果没有key值,会按照规则自动生成
if (cache[key]) {
//有缓存
//重用组件实例
vnode.componentInstance = cache[key].componentInstance
remove(keys, key); //删除key值
//将key值加入到数组末尾,这样是为了保证最近使用的组件在数组中靠后,主要是为了方便设置的max值删除很久没使用的组件
keys.push(key)
} else {
//没有缓存的则进行缓存
cache[key] = vnode
keys.push(key)
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
//超过最大缓存数量,移除第一个key对应的缓存
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}