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

686 阅读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) {
    // 进入页面时,只要设置过 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>