《Fantastic-admin 技术揭秘》系列将带你了解 Fantastic-admin 这款框架各种功能的设计与实现。通过了解这些技术细节,你不光可以更轻松地使用 Fantastic-admin 这款框架,也可以在其他项目中使用这些技术。
你可以点击 这里 查看本系列的所有文章,也欢迎你在评论区留言告诉我你感兴趣的内容,或许下一篇文章就会带你揭秘其中的奥秘。
前言
在后台管理系统中,多级路由缓存一直是个老生常谈的话题,我 4 年前写的《一劳永逸,解决基于 keep-alive 的后台多级路由缓存问题》这篇文章,如今依旧有人点赞收藏。
但随着 vue-router 的更新,如今已经有了更好的解决方案。
分析需求
路由不管配置成两级还是多级,都可以全局控制任意路由的缓存。
原有方案
也就是 4 年前我提供的方案,如果你不想了解,可以跳过这一小节。
首先我们知道 KeepAlive
组件是用于缓存子组件而设计的,而父子组件的嵌套关系,和使用 RouterView
组件来嵌套子路由的场景刚好吻合。
通常我们也是这么使用的:
<script setup lang="ts">
import { useKeepAliveStore } from '@/store/modules/keepAlive';
// keepAliveStore.list 存放需要缓存路由对应组件的组件名
const keepAliveStore = useKeepAliveStore();
</script>
<template>
<RouterView v-slot="{ Component, route }">
<KeepAlive :include="keepAliveStore.list">
<component :is="Component" :key="route.fullPath" />
</KeepAlive>
</RouterView>
</template>
但它并不支持多级路由,因为多级路由想要让页面正常显示,每个父级路由都需要配置 KeepAlive
组件。
+------------------------------+
| Layout |
| +------------------------+ |
| | EmptyLayout | |
| | +------------------+ | |
| | | Page | | |
| | +------------------+ | |
| +------------------------+ |
+------------------------------+
就像上面这样,需要在 Layout
组件和 EmptyLayout
组件中都配置 KeepAlive
和 RouterView
组件,才能让页面正常显示。
即便这样,它依旧存在问题,因为如果不缓存 EmptyLayout
组件,则 EmptyLayout
下的页面将无法被缓存;但如果缓存了 EmptyLayout
组件,EmptyLayout
下的所有页面又都会被缓存,无法做单独的控制。
所以我在当时提出了一个方案,既然多级路由无解,两级路由又完全没问题,那就将多级路由结构,全部转换成两级路由结构就好了。
+------------------------------+ +------------------------------+
| Layout | | Layout |
| +------------------------+ | | +------------------------+ |
| | EmptyLayout | | +------> | | Page | |
| | +------------------+ | | | | | |
| | | Page | | | | | | |
| | +------------------+ | | | | | |
| +------------------------+ | | +------------------------+ |
+------------------------------+ +------------------------------+
从数据结构上来看,就是这样:
// 原始数据
{
path: '/users',
meta: {
title: '用户管理'
},
children: [
{
path: 'clients',
meta: {
title: '客户管理'
},
children: [
{
path: 'list',
meta: {
title: '客户列表'
}
},
{
path: 'detail',
meta: {
title: '客户详情'
}
}
]
}
]
}
// 处理后数据
{
path: '/users',
meta: {
title: '用户管理'
},
children: [
{
path: 'clients/list',
meta: {
title: '客户列表',
breadcrumb: [
{ path: '/users', title: '用户管理' },
{ path: '/users/clients', title: '客户管理' },
{ path: '/users/clients/list', title: '客户列表' }
]
}
},
{
path: 'clients/detail',
meta: {
title: '客户详情',
breadcrumb: [
{ path: '/users', title: '用户管理' },
{ path: '/users/clients', title: '客户管理' },
{ path: '/users/clients/detail', title: '客户详情' }
]
}
}
]
}
这部处理并不难,通过递归遍历路由数据就可以实现。
需要额外注意的是,因为数据结构变了,原先多级路由可以通过 $route.matched
实现面包屑导航的功能,现在需要手动增加一个 breadcrumb
字段来实现。
最新方案
简单来说,最新方案无需做任何处理,因为 vue-router 4.1 版本提供了一个新特性:忽略父组件。
具体使用方法如下:
// 原始数据
{
path: '/users',
component: () => import('@/Layout.vue'),
meta: {
title: '用户管理'
},
children: [
{
// 注意看,这一层级的路由,没有设置 component
path: 'clients',
meta: {
title: '客户管理'
},
children: [
{
path: 'list',
component: () => import('@/views/list.vue'),
meta: {
title: '客户列表'
}
},
{
path: 'detail',
component: () => import('@/views/detail.vue'),
meta: {
title: '客户详情'
}
}
]
}
]
}
由于中间 clients
这层路由没有指定路由组件,顶级 Layout
组件里的 <RouterView>
将跳过它并直接显示 list
或 detail
子路由组件。
这样原有路由的数据结构无需做任何改动,而 <RouterView>
组件处理的其实就是一个二级的嵌套结构,所以缓存问题也迎刃而解了。
并且得益于这个特性,面包屑导航功能也能通过 $route.matched
实现。
扩展
为了避免用户误配置了中间层级路由的 component
字段,导致缓存失效,最好还是将路由数据整体清洗一遍,将中间层级的 component
字段删除。
// 删除路由中间层级对应的组件
function deleteMiddleRouteComponent(routes: RouteRecordRaw[], isRoot = true) {
const res: RouteRecordRaw[] = []
routes.forEach((route) => {
if (!isRoot && route.children) {
delete route.component
route.children = deleteMiddleRouteComponent(route.children, false)
}
res.push(route)
})
return res
}