一、< keep-alive >
< keep-alive > 用来缓冲插槽中的内容(缓冲后不会重新渲染),就是 < keep-alive >……< / keep-alive>标签包裹的内容,里面只允许一个根标签,多个根标签只缓冲第一个标签,如:
<keep-alive>
<div>root1</div>
<p>root2</p>
……
</keep-alive>
上面有多个根节点情况,< keep-alive > 只会缓冲第一个根节点。< keep-alive > 自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。当在 < keep-alive >中切换缓冲过的组件时,会触发 activated 和 deactivated 这两个生命周期钩子函数。
注:<keep-alive>常包裹路由视图<router-view>或动态组件<component :is="">来使用
二、清除 < keep-alive > 缓冲的组件
清除缓冲有两种方案:1. 使用 < keep-alive > 标签提供的 include、exclude 属性;2. 操作 < keep-alive > 组件内部的缓冲数据 cache, keys
清除缓冲的场景:打开多个子页面,每个子页面切换时,保留每个子页面状态(缓冲),手动关闭后(清除缓冲),重新打开要新加载。如下图场景:
- include, exclude, max
include:控制哪些组件能够缓冲,在其匹配值内的组件才能缓冲。
exclude:控制哪些组件不能够缓冲,在其匹配值内的组件不能缓冲。
include 和 exclude 可单独使用,也可同时使用,若同时使用有相同值时,则该组件不会被缓冲,因为 exclude 优先级高于 include。
max:最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。
include 和 exclude 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示,无论哪种形式数据,其值都是针对组件名称,就是给组件命名的 name 值,也就是说使用这两个属性控制组件是否缓冲,组件必须要命名。常用数组作为这两个属性值,数组元素就是命名的组件名称。这两个属性使用实例:
<!-- 逗号分隔字符串 -->
<keep-alive include="a,b">
<component :is="view"></component>
</keep-alive>
<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>
<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
<component :is="view"></component>
</keep-alive>
清除缓冲: 操作 include 或者 exclude 都行,例如:可以给 include 绑定一个数组,该数组来自 Vuex 的 state,这样能够进行全局管理组件是否缓冲,数组初始值则为所有需要缓冲的组件名称列表。清除缓冲时,只需要将不缓冲的组件名称从数组中移除即可(注:再次进入该组件之前,必须要将其名称加入到数组中,不然不会缓冲,因为你上一步把它从数组中移除了
)。< keep-alive > 源码实现中有对应的 include 和 exclude 属性监听器,两者属性值变化时会动态处理 cache 和 keys 将哪些不需要缓冲的组件移除。
- cache, keys
想弄明白操作 cache、keys 清除缓冲原理,需要了解 < keep-alive > 组件的源码实现,可阅读这篇文章< keep-alive >源码解读
我这里可以和你们简单说下:< keep-alive > 内部将需要缓冲的组件都放在了 cache 对象里,缓冲的组件所绑定的key值会作为 cache 对象的键名,若组件未绑定key,内部会有一个生成键名的规则,该规则会结合组件实例 componentOptions 中的 cid 生成一个key 作为 cache 的键名(如下图控制台打印出来的虚拟DOM中的 cid)。cache 对象中键名对应的键值则为当前组件的虚拟DOM(里面的 elm 属性值存放的是组件DOM)。< keep-alive > 中的 keys 是一个列表,用来存放已经缓冲组件的 key,也就是 cache 的键名,keys 列表采用 LRU 算法进行更新维护,将最近最少使用的缓冲组件放在 keys 头部,最新使用的放在末尾。为什么还要多加一个 keys 存放缓冲组件key?主要是想缓冲组件达到一定数量时,或者使用 max 属性时,保证删除的是最近最少使用的组件,来释放空间。
清除缓存: 给 < keep-alive > 包裹的组件绑定 ref(假设 ref 值为 routerView),通过 this.$refs.routerView. $ vnode.parent.componentInstance 获取 < keep-alive > 的 VNode 中的组件实例 componentInstance 属性,在控制台打印出来如下图,可看到 cache, keys 属性,将这俩参数浅拷贝出来,为什么不直接给 < keep-alive > 绑定 ref 获取?因为尝试过获取不到。。。缓冲的组件必须要绑定 key ,因为不绑定 key ,< keep-alive > 内部帮我们生成的 key ,我们无法得知,就没办法手动根据 key 删除指定缓冲组件。我在项目里是用路由组件对应的路由地址作为 key,项目代码在文末。删除指定缓冲的组件时,通过组件 key ,删除 cache 的对应 key 属性,dom被删除,keys 列表也要同步更新,删除对应 key。
<template>
<transition name="fade-transform" mode="out-in">
<keep-alive>
<router-view ref="routerView" :key="$route.path" />
</keep-alive>
</transition>
</template>
<script>
import { $bus } from '@utils/eventBus.js'
export default {
data() {
return {
cachePage: [],
}
},
mounted() {
this.$nextTick(() => {
// 打印的keep-alive组件实例在上图
console.log(this.$refs.routerView.$vnode.parent.componentInstance)
$bus.$on('removeCachePage', key => {
/**
* @param {Object} cache:类数组对象,存放缓冲的路由组件,键值对形,key为路由路径,value为路由组件Vnode
* @param {Array} keys:数组,存放缓冲的路由组件的key,即路由路径
* @param {String} key:字符串,路由组件的key,指定要删除的路由组件key值
*/
const { cache, keys } = this.$refs.routerView.$vnode.parent.componentInstance
Object.prototype.hasOwnProperty.call(cache, key) &&
(() => {
// 点击tab关闭页面,移除对应页面缓冲
delete cache[key]
keys.splice(keys.indexOf(key), 1)
console.log('%c 删除缓冲页面成功!', 'color:red')
})()
})
})
},
beforeDestroy() {
$bus.$off('removeCachePage')
},
}
</script>
<style scoped lang="less"></style>