【Fantastic-admin 技术揭秘】精细化控制页面缓存

1,430 阅读3分钟

《Fantastic-admin 技术揭秘》系列将带你了解 Fantastic-admin 这款框架各种功能的设计与实现。通过了解这些技术细节,你不光可以更轻松地使用 Fantastic-admin 这款框架,也可以在其他项目中使用这些技术。

你可以点击 这里 查看本系列的所有文章,也欢迎你在评论区留言告诉我你感兴趣的内容,或许下一篇文章就会带你揭秘其中的奥秘。

前言

在《关于 KeepAlive 多级路由缓存问题的终极解决方案》文章中,已经实现了页面缓存的功能。

但在实际使用中,业务可能会要求某个页面根据不同的条件,来决定是否缓存。

所以今天就来聊聊,如何精细化控制页面缓存。

需求分析

vue-router 提供了 meta 属性,方便我们在路由中配置一些自定义信息。而我们也是在 meta 属性中约定好控制页面缓存的字段。

精细化控制我总结了有这 3 个维度:

  1. 始终缓存

A 页面默认缓存,无论进入任何页面,A 页面始终缓存。

  1. 进入指定页面时进行缓存

A 页面默认不缓存,但进入 B 页面时,则 A 页面需要缓存。

  1. 进入指定页面时清除缓存

A 页面默认缓存,但进入 B 页面时,则 A 页面需要清除缓存。

根据上面维度,可以确定好需要的 meta 属性:

meta: {
  cache: boolean | string | string[]
  noCache: string | string[]
}
  • cachetrue 时,表示该页面始终缓存
  • cachestring | string[] 时,表示该页面在进入指定页面时进行缓存,可设置多个指定页面
  • cachetrue 时,并且当 noCachestring | 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>