背景
我们在开发那种后台管理类的项目,需要那种多 tab 页签的功能,项目需要缓存已打开的页面,如果关闭页面重新打开,就重新加载。
如果是正常的页面,我们用 vue 提供的 keep-alive 配合 include 功能就可以了,如果是一些类似于详情页之类的通用页面,页面内容由外部传进来的 id 等数据来请求接口数据进行渲染的,并且一个页面可以打开很多次,那么这些由同一个页面多次打开,并且需要分别缓存的场景,上面的方法就有问题了,比如关闭页面缓存没有删掉等一些问题。
这个问题主要的原因页面的 name 引起的,一个页面打开 N 次,这些页面的 name 都是一个,而 vue 底层的 keep-alive 是监听 include 和 exclude 的变化,来重新计算缓存的。
如果我们的 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 方法包一层就行了。
到这里,同一个页面,多次打开,分别缓存的功能就完成了。