序
在系统开发中,我们会通过路由划分模块。在路由来回切换时候,通常需要我们保存该页的状态。这一需求在具有表单填写、Tab切换、表格分页功能的页面上,尤为突出。
如何在系统中,随心所欲地控制缓存,是开发者必然要面对的问题。
改造路由
以一个传统的后台管理模板为例
通常中间的 RouterView 通常是有缓存需求的。
因此根据官方的用例,对Layout 中的 RouterView 中使用 KeepAlive :
<RouterView v-slot="{ Component }">
<KeepAlive>
<component :is="Component"></component>
</KeepAlive>
</RouterView>
经过改造,来回切换路由时,发现我们已经可以正常缓存路由了。
KeepAlive
接下来,需要直面的问题是,并不是所有路由都需要缓存。
查阅官网 KeepAlive , 可知 include/exclude 可以帮助我们解决这一问题。
include
如果我们采用 include ,优势是可以清楚地知晓我们已缓存了哪些组件。
那么不需要缓存某个组件,只需要将他从数组中剔除即可。
根据官网的描述 , include 需要提供的是实际 路由组件 的 name, 并非 route 的 name 。
路由组件指的是 路由数据中 component 字段 import 的组件
如此问题变成了,如何收集路由组件的 name 并管理。
这是一个具有全局性质的数据,我们很容易想到使用 Pinia 来管理。
通过 Pinia 管理
定义好 添加/删除 方法
export const useKeepAliverStore = defineStore('keepAliver', () => {
const includeSet = ref(new Set<string>())
const include = computed(() => Array.from(includeSet.value))
const addInclude = (name: string) => {
includeSet.value.add(name)
}
const removeInclude = (name: string) => {
includeSet.value.delete(name)
}
return {
include,
addInclude,
removeInclude,
}
})
<RouterView v-slot="{ Component }">
<KeepAlive :include="keepAliverStore.include">
<component :is="Component"></component>
</KeepAlive>
</RouterView>
动态收集组件名称
可以通过监听路由变化,将组件名称动态地收集到 include 中
// useKeepAliverStore
const collectingInclude = () => {
const route = useRoute()
return watch(() => route.matched, async (matched) => {
const item = matched.at(-1)
if (!item) return
if (item.instances) {
// item.instances中的值是异步的添加的,所以需要有值后再执行下一步
await waiting(() => isNotEmptyObject(item.instances))
for (const key in item.instances) {
const instance = item.instances[key]
const vm = instance?.$
if (vm) {
vm.type.name && addInclude(vm.type.name)
}
}
}
}, {
immediate: true,
})
}
根据以上思路改造好的 Layout 大致是这样的:
<script lang="ts" setup>
const keepAliverStore = useKeepAliverStore()
// 监听路由,自动添加include
keepAliverStore.collectingInclude()
</script>
<template>
...
<ElScrollbar ref="scrollbarNode">
<RouterView v-slot="{ Component }">
<KeepAlive :include="keepAliverStore.include">
<component :is="Component"></component>
</KeepAlive>
</RouterView>
</ElScrollbar>
...
</template>
通过路由数据按需缓存
通常情况下,我们希望通过路由元数据来决定是否自动缓存某个路由组件
declare module 'vue-router' {
interface RouteMeta {
/**
* 是否缓存
* 0: 缓存
* 1: 不缓存
* 2: 自定义缓存策略(预留)
*/
noCache?: 0|1|2
// ...
}
}
在自动收集的监听中加入判断
const collectingInclude = () => {
const route = useRoute()
return watch(() => route.matched, async (matched) => {
const item = matched.at(-1)
if (!item) return
// +++++++++++++++++++++++++++++++++++
if (item.meta.noCache == 1) return
if (item.instances) {
// item.instances中的值是异步的添加的,所以需要有值后再执行下一步
await waiting(() => isNotEmptyObject(item.instances))
for (const key in item.instances) {
const instance = item.instances[key]
const vm = instance?.$
if (vm) {
vm.type.name && addInclude(vm.type.name)
}
}
}
}, {
immediate: true,
})
}
在路由数据中加入标识
{
meta: {
title: '多列表单',
noCache: 1,
},
path: 'multiple',
component: ...
}
观察 include 数组,可验证 noCache: 1 的路由组件将不会被自动收集,即该组件不会被缓存
结
经过上述改造。实战过程中,面对一般业务,我们只需要关注
-
给需要缓存的 路由组件 加上
name不加
name的路由组件因为不能被 include 收录,所以不被缓存 -
在路由数据
meta中设置noCache: 1来确保该路由组件不被缓存
但也有一些边界情况值得我们讨论
- 如何缓存页面中的滚动条进度
- 如何在对同一个页面按需缓存
如果感兴趣,我们可以在一章里讨论,也可在评论区留下思路。