《Fantastic-admin 技术揭秘》系列将带你了解 Fantastic-admin 这款框架各种功能的设计与实现。通过了解这些技术细节,你不光可以更轻松地使用 Fantastic-admin 这款框架,也可以在其他项目中使用这些技术。
你可以点击 这里 查看本系列的所有文章,也欢迎你在评论区留言告诉我你感兴趣的内容,或许下一篇文章就会带你揭秘其中的奥秘。
前言
在《关于 KeepAlive 多级路由缓存问题的终极解决方案》文章中,已经实现了页面缓存的功能。
但在实际使用中,业务可能会要求某个页面根据不同的条件,来决定是否缓存。
所以今天就来聊聊,如何精细化控制页面缓存。
需求分析
vue-router 提供了
meta
属性,方便我们在路由中配置一些自定义信息。而我们也是在meta
属性中约定好控制页面缓存的字段。
精细化控制我总结了有这 3 个维度:
- 始终缓存
A 页面默认缓存,无论进入任何页面,A 页面始终缓存。
- 进入指定页面时进行缓存
A 页面默认不缓存,但进入 B 页面时,则 A 页面需要缓存。
- 进入指定页面时清除缓存
A 页面默认缓存,但进入 B 页面时,则 A 页面需要清除缓存。
根据上面维度,可以确定好需要的 meta
属性:
meta: {
cache: boolean | string | string[]
noCache: string | string[]
}
- 当
cache
为true
时,表示该页面始终缓存 - 当
cache
为string | string[]
时,表示该页面在进入指定页面时进行缓存,可设置多个指定页面 - 当
cache
为true
时,并且当noCache
为string | string[]
时,表示该页面在进入指定页面时清除缓存,可设置多个指定页面
实现方案
需求确定后,实现就不难了。
不过在此之前,需要借助 pinia 来存储被缓存页面的组件名。
const useKeepAliveStore = defineStore(
'keepAlive',
() => {
const list = ref<string[]>([])
function add(name: string) {
!list.value.includes(name) && list.value.push(name)
}
function remove(name: string) {
list.value = list.value.filter((v) => {
return v !== name
})
}
return {
list,
add,
remove,
}
},
)
export default useKeepAliveStore
然后需要在 router.afterEach
中进行实现。
router.afterEach((to, from) => {
const keepAliveStore = useKeepAliveStore()
if (to.fullPath !== from.fullPath) {
// 进入页面时,只要设置过 cache ,就将当前页面组件的 name 信息存入 keep-alive 全局状态
if (to.meta.cache) {
const componentName = to.matched.at(-1)?.components?.default.name
if (componentName) {
keepAliveStore.add(componentName)
}
else {
console.warn('该页面组件未设置组件名,会导致缓存失效,请检查')
}
}
// 离开页面时,根据 cache 和 noCache ,判断是否需要删除 keep-alive 全局状态里离开页面组件的 name 信息
if (from.meta.cache) {
const componentName = from.matched.at(-1)?.components?.default.name
if (componentName) {
// 通过 meta.cache 判断针对哪些页面进行缓存
switch (typeof from.meta.cache) {
case 'string':
if (from.meta.cache !== to.name) {
keepAliveStore.remove(componentName)
}
break
case 'object':
if (!from.meta.cache.includes(to.name as string)) {
keepAliveStore.remove(componentName)
}
break
}
// 通过 meta.noCache 判断针对哪些页面不需要进行缓存
if (from.meta.noCache) {
switch (typeof from.meta.noCache) {
case 'string':
if (from.meta.noCache === to.name) {
keepAliveStore.remove(componentName)
}
break
case 'object':
if (from.meta.noCache.includes(to.name as string)) {
keepAliveStore.remove(componentName)
}
break
}
}
}
}
}
})
最后,使用 KeepAlive
组件实现整体缓存逻辑。
<script setup lang="ts">
import { useKeepAliveStore } from '@/store/modules/keepAlive';
const keepAliveStore = useKeepAliveStore();
</script>
<template>
<RouterView v-slot="{ Component, route }">
<KeepAlive :include="keepAliveStore.list">
<component :is="Component" :key="route.fullPath" />
</KeepAlive>
</RouterView>
</template>
扩展
Vue 组件名则可以这样设置:
<script lang="ts">
export default {
name: 'ComponentName',
}
</script>
<script setup lang="ts">
...
</script>
从 Vue 3.3 开始,可以使用 defineOptions
在 <script setup>
里定义组件名:
<script setup lang="ts">
defineOptions({
name: 'ComponentName',
})
</script>