keep-alive是什么?
keep-alive 是vue的一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例。
常用场景:
- 路由切换
- 动态组件切换
- 需要缓存表单,搜索项或其他组件状态等
如何使用keep-alive?
在大多数时候,我们经常与动态组件(component,is)配合使用。
<router-view v-slot="{ Component,route }">
<keep-alive">
<component :is="Component" :key="route.fullPath" />
</keep-alive>
</router-view>
include(包含)
keep-alive默认会缓存包裹的所有组件,但是如果设置include的话,就会缓存include字段指定名称的组件,具体使用可以通过以下几种方式。
<!-- 以英文逗号分隔的字符串 -->
<KeepAlive include="a,b">
<component :is="view" />
</KeepAlive>
<!-- 正则表达式 (需使用 `v-bind`) -->
<KeepAlive :include="/a|b/">
<component :is="view" />
</KeepAlive>
<!-- 数组 (需使用 `v-bind`) -->
<KeepAlive :include="['a', 'b']">
<component :is="view" />
</KeepAlive>
exclude(排除)
keep-alive默认会缓存包裹的所有组件,但是如果设置exclude的话,就会缓存exclude字段指定名称以外的组件,具体使用方式和include字段相同。
max(最大缓存数)
因为keep-alive是把组件的vnode以键值对的形式保存起来,那么就会占用一定内存。所以我们在使用的时候要根据实际项目情况和用户的电脑配置灵活的限制最大缓存数。
生命周期
如果说我们希望在动态组件切换的时候,除了记住上次操作的一些状态,也希望去通过后端接口拉取最新数据,我们可以调用keep-alive组件提供的2个生命周期函数。
onActivated
调用时机为首次挂载,以及每次从缓存中被重新插入时。
onDeactivated
从DOM 上移除、进入缓存以及组件卸载时调用。
在我的业务上是如何使用的?
业务背景
先说一下我们这边的业务场景,类似于一个工作台,可以自定义顶部的菜单,并且支持删除。
同时我们的界面分为低代码界面和正常vue开发的界面,也希望支持页面缓存。
那么其中有几个关键点需要进行处理:
- 如何管理自定义菜单?
- 如何实现amis这种,单一路由地址请求后台数据动态渲染界面或者动态路由?(类似于/amisPage?id=10086)
实现
首先我先定义了一个全局store,用来维护自定义菜单,具体存储的数据类型如下。
export enum EnumPageType {
vue = 'VUE', // vue开发界面
amis = 'AMIS', // amis开发的界面
iframe = 'IFRAME' // iframe配置界面
}
export type MenuItemType = Partial<{
id: string, // 页面id
name: string, // 页面名称
type: EnumPageType, // 页面类型
path: string, // 路由地址
icon: string, // 页面icon
isCache: boolean // 是否进行缓存
children: MenuItemType[] // 子页面
}>
export type CustomMenusType = {
current: string|undefined, // 当前访问的路径
list: MenuItemType[] // 全部自定义菜单
}
在获取菜单数据的时候,我们根据页面类型,统一封装下我们后续需要的path。
export const transferMenuList = (menus) => {
return menus.map(item => {
const isAmis = isAmisPage(item)
let path = item.path
if (isAmis) {
path = `/amisPage?amisPageId=${item.id}`
}
return {
...item,
path,
children: transferMenuList(item.children || [])
}
})
}
针对菜单容器进行路由内容展示,设置keepAlive的组件为动态的,方便在关闭菜单的时候清空缓存的数据。
注意:要保证key不一样,否则可能会同时生成多个相同组件,无法实现子路由缓存。
<router-view v-slot="{Component,route}">
<div v-loading="loading">
<keep-alive :include="custom.list.map(item=>item.path) as string[] ">
<component v-if="isCache" :is="Component" :key="route.fullPath" />
</keep-alive>
<component v-if="!currentMenuConfig?.openCache" :is="Component" :key="route.fullPath" />
</div>
</router-view>
对于keepAlive如果不设置include或exclude属性,那默认就是包裹的所有组件都缓存。但是如果设置了include属性,那么就只有include指定的组件名称列表才会被缓存,那我们如果保证对应path的组件名称一定是path?
vue的组件名称默认是当前组件文件名,但是很多同学相信一定写过index.vue,那么我们总不能要求每个同学通过defineOptions手动定义组件名称。
我们可以对component进行包裹,动态生成一个名称为对应path的组件。
<component v-if="isCache" :is="wrap(route.fullPath, Component)" :key="route.fullPath" />
import { h } from "vue";
const wrapperMap = new Map();
//使用key值作为名字给即将渲染的组件包裹一层壳来保证被Vue正常缓存
export const wrap = (fullPath, component) => {
let wrapper;
const wrapperName = fullPath; //壳组件的名字,路由的路径是唯一的
//判断是否已经存在包裹好的组件
if (wrapperMap.has(wrapperName)) {
wrapper = wrapperMap.get(wrapperName);
} else {
//包裹组件
wrapper = {
name: wrapperName,
render() {
return h('div', component);
},
};
//保存包裹后的组件
wrapperMap.set(wrapperName, wrapper);
}
return h(wrapper);
};
如果内部包含子路由,那每次子路由调整,对用path改变,是不是缓存也失效了? 我们这里对path进行统一处理,比如/a/b取/a为缓存key。
export const getCachePath = (fullPath) => {
let path = fullPath.replace(/\/[a-zA-Z0-9]+(\?[a-zA-Z0-9=&]*)?$/, '')
if (!path) {
path = fullPath
}
return path
}
如何查看缓存的组件数据
const keepAliveRef=ref()
<button @click="console.log('keepAliveRef',keepAliveRef.$.__v_cache)">测试</button>
<keep-alive ref="keepAliveRef"></keep-alive>
keepAliveRef.$.__v_cache 就是我们缓存的数据。
这里建议配合 vuetools 一起使用,可以查看keepAlive下面有哪些组件。也可以查看该组件的名称和组件内对应的属性。
原理
这部分现在网上的文章很多,这里就不多说啦。
就是把组件的vNode数据以对象的形式保存起来,key为组件名称。在切换的时候,获取缓存数据,再呈现该组件。