解决 vue3 一个页面多次打开分别缓存的问题

1,084 阅读2分钟

背景

我们在开发那种后台管理类的项目,需要那种多 tab 页签的功能,项目需要缓存已打开的页面,如果关闭页面重新打开,就重新加载。

如果是正常的页面,我们用 vue 提供的 keep-alive 配合 include 功能就可以了,如果是一些类似于详情页之类的通用页面,页面内容由外部传进来的 id 等数据来请求接口数据进行渲染的,并且一个页面可以打开很多次,那么这些由同一个页面多次打开,并且需要分别缓存的场景,上面的方法就有问题了,比如关闭页面缓存没有删掉等一些问题。

这个问题主要的原因页面的 name 引起的,一个页面打开 N 次,这些页面的 name 都是一个,而 vue 底层的 keep-alive 是监听 include 和 exclude 的变化,来重新计算缓存的。

image.png

如果我们的 include 和 exclude 里面放的是 vue 组件的 name 值,就会有缓存问题,所以这里的根本解决方法就是把这个 vue 组件的 name 值换成一个唯一的值就行,比如 route 的 fullpath。

第一步

要重新设置 keep-alive 的 include 的值,vue 默认的是用的页面的 name,这里我们要手动改成页面的 fullpath。

因为页面上有已打开的页面标签列表,所以我们需要监听 route.fullPath,收集页面路径就行:

const pageList = ref([])

watch(
    () => route.fullPath,
    (fullPath: any) => {
        // 这里需要去更新页面列表内的数据
        // 更新 pageList:新增
        pageList.value.push(fullpath)
    }
)

然后把这个 pageList 给 keep-alive 的 include 就行。

第二步

第一步已经把 keep-alive 的 include 的值设置成 route.fullpath 了,第二步就需要重写 vue 组件的 name 值,把 name 值也设置成 fullpath,因为 include 的值和组件的 name 必须一致,不然缓存无效。

// 重写 vue 组件的 name
import { defineComponent, h } from 'vue'

const pages = new Map<string, any>()

/**
 * component: vue 组件
 * key:页面的 fullpath
 **/
export const componentWrap = (component: any, key: string) => {
  if (!component) {
    return component
  }
  const componentName = component.type.name
  if (pages.has(key)) {
    return pages.get(key)
  }

  if (key !== componentName) {
    // 这里为 name 重写的具体方法
    const com = h(defineComponent({
      name: key,
      render() {
        return h(component)
      }
    }))
    pages.set(key, com)
    return com
  }
  return component
}

使用

include 和 vue 组件的 name 重写完了,最后就是怎么使用了,如下:

<template>
  <router-view v-slot="{ Component, route }">
    <keep-alive :include="pageList">
      <component :is="componentWrap(Component, route.fullPath)" :key="route.fullPath" />
    </keep-alive>
  </router-view>
</template>

使用就很简单了,把 router-view 抛出来的 component 用 componentWrap 方法包一层就行了。

到这里,同一个页面,多次打开,分别缓存的功能就完成了。