《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) {
if (to.meta.cache && !to.meta.iframe) {
const componentName = to.matched.at(-1)?.components?.default.name
if (componentName) {
// 缓存当前页面前,先判断是否需要进行清除缓存,判断依据:
// 1. 如果 to.meta.cache 为 boolean 类型,并且不为 true,则需要清除缓存
// 2. 如果 to.meta.cache 为 string 类型,并且与 from.name 不一致,则需要清除缓存
// 3. 如果 to.meta.cache 为 array 类型,并且不包含 from.name,则需要清除缓存
// 4. 如果 to.meta.noCache 为 string 类型,并且与 from.name 一致,则需要清除缓存
// 5. 如果 to.meta.noCache 为 array 类型,并且包含 from.name,则需要清除缓存
// 6. 如果是刷新页面,则需要清除缓存
let shouldClearCache = false
if (typeof to.meta.cache === 'boolean') {
shouldClearCache = !to.meta.cache
}
else if (typeof to.meta.cache === 'string') {
shouldClearCache = to.meta.cache !== from.name
}
else if (Array.isArray(to.meta.cache)) {
shouldClearCache = !to.meta.cache.includes(from.name as string)
}
if (to.meta.noCache) {
if (typeof to.meta.noCache === 'string') {
shouldClearCache = to.meta.noCache === from.name
}
else if (Array.isArray(to.meta.noCache)) {
shouldClearCache = to.meta.noCache.includes(from.name as string)
}
}
if (from.name === 'reload') {
shouldClearCache = true
}
if (shouldClearCache) {
keepAliveStore.remove(componentName)
await nextTick()
}
keepAliveStore.add(componentName)
}
else {
// turbo-console-disable-next-line
console.warn('该页面组件未设置组件名,会导致缓存失效,请检查')
}
}
}
})
最后,使用 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>