文章中以Vue框架为例
常规路由
src/router/index.js
// src/router/index.js
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'index',
component: () => import('../views/index.vue'),
meta: {
title: '首页'
}
},
{
path: '/about',
name: 'about-index',
component: () => import('../views/about/index.vue'),
meta: {
title: '关于'
}
},
...
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: routes
})
export default router
常规开发中,我们需要手动去设置修改 routes
的值来完成对路由变化的更新,这无疑增加了我们重复的开发工作以及维护牺牲,所以该篇文章提出了自动化改写方案避免此类没有必要的浪费。
思路
此处要提到团队开发中常用到的一个概念:约定大于配置
,当我们可以通过团队约定的某些规范,来优化或规避各类手动配置项时,优先采用约定规范的方案实现。
一般情况下,我们开发项目的目录结构往往就可以体现出我们的路由结构,如下图:
Project
|
|--src--views
| |--index.vue
| |--About
| |--index.vue
那么我们实际可以利用文件目录来实现动态生成路由配置,为此我们给每一个路由对应的组件目录下增加一个 page.ts
文件,来辅助完成路径获取,并传递额外的值。
src/views/About/page.ts
export default {
title: '关于'
}
工具
// Webpack环境下
require.context('../views', true, /\page.ts$/)
// Vite环境下
const pages = import.meta.glob('../views/**/page.ts', { eager: true, import: 'default' })
通过以上两个方法,可以扫描对应文件,并返回路径与文件内容组成的对象。
改写完成后(一级路由)
src/router/index.ts
import {
createRouter,
createWebHistory,
type RouteMeta,
type RouteRecordRaw,
type RouteComponent
} from 'vue-router'
const pages: Record<string, RouteMeta | undefined> = import.meta.glob('../views/**/page.ts', {
eager: true,
import: 'default'
})
const comps: Record<string, RouteComponent> = import.meta.glob('../views/**/index.vue')
const routes: Array<RouteRecordRaw> = Object.entries(pages).map(([path, page]) => {
const originPath = path
const compPath = originPath.replace('page.ts', 'index.vue')
path = path.replace('../views', '').replace('/page.ts', '').toLowerCase() || '/'
const name =
path
.split('/')
.filter((p) => p)
.join('-') || 'index'
return {
path,
name,
component: comps[compPath],
meta: page
}
})
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: routes
})
export default router
以上改写只支持了一级路由的情况,若有多级路由的需求,还需另外改写
多级路由改写
src/interfaces/common.ts
// 路由组件配置文件格式
export interface iPage {
path?: string // 用于辅助动态生成时类型检查,各page.ts中不填写
parent?: string // 父组件path
name: string // 路由name
level: number // 路由等级
title?: string // 所需传参,此处以title为例
}
src/router/index.ts
import type { iPage } from '@/interface/common'
import {
createRouter,
createWebHistory,
type RouteMeta,
type RouteRecordRaw,
type RouteComponent
} from 'vue-router'
interface iRouteMeta extends RouteMeta {
name: string
level: number
}
const pages: Record<string, iRouteMeta> = import.meta.glob('../views/**/page.ts', {
eager: true,
import: 'default'
})
const comps: Record<string, RouteComponent> = import.meta.glob('../views/**/index.vue')
// 格式化pages对象为数组
const arrPages: iPage[] = Object.entries(pages).map(([path, page]) => ({
path,
...page
}))
// 动态初始化路由配置 Routes
const generateRoutes = (arr: iPage[], level: number, parent?: string): Array<RouteRecordRaw> => {
const routes = arr.filter((item) => item.level === level && (!parent || item.parent === parent))
return routes.map((item) => {
const path = item.path?.replace('../views', '').replace('/page.ts', '').toLowerCase() || '/'
const name = item.name
const component = item.path ? comps[item.path.replace('page.ts', 'index.vue')] : null
const meta = {
title: item.title
}
const children = generateRoutes(arr, level + 1, item.parent)
return {
path,
name,
component,
meta,
children
}
})
}
const routes: Array<RouteRecordRaw> = generateRoutes(arrPages, 1)
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: routes
})
export default router
评论区