无论是 vue-router 还是 react-router,路由配置都是前端项目的核心:path、name、component、meta、children、redirect 等字段构成了路由表。路由配置天然是递归结构(children 嵌套子路由),用 TypeScript 类型化后,可以约束 path 格式、meta 字段结构、name 唯一性,甚至从路由配置推导出所有路由名的联合类型,在编程式导航时享有完整的类型提示。
// 路由配置类型:递归嵌套 + meta 强类型 + 路由名联合推导
// children 递归引用自身,meta 泛型化,从配置推导出 name 联合
// 1. 路由元信息基础类型
interface RouteMetaBase {
title?: string;
icon?: string;
requiresAuth?: boolean;
roles?: string[];
keepAlive?: boolean;
hidden?: boolean;
}
// 2. 路由配置:泛型 M 约束 meta 类型,递归 children
interface RouteConfig<M = RouteMetaBase> {
path: string;
name?: string;
component?: any;
redirect?: string;
meta?: M;
children?: RouteConfig<M>[]; // 递归引用:子路由与父路由同类型
props?: boolean | Record<string, any> | ((...args: any[]) => Record<string, any>);
alias?: string | string[];
beforeEnter?: (...args: any[]) => any;
}
// 3. 从路由配置数组中提取所有 name 的联合类型
type RouteNames<T extends readonly RouteConfig[]> =
T[number] extends infer R
? R extends { name: string; children?: infer C }
? C extends readonly RouteConfig[]
? R['name'] | RouteNames<C>
: R['name']
: never
: never;
// 4. 自定义 meta 类型
interface AppRouteMeta extends RouteMetaBase {
title: string; // 自定义:title 必填
affix?: boolean; // 自定义:是否固钉
breadcrumb?: boolean; // 自定义:是否显示面包屑
activeMenu?: string; // 自定义:高亮菜单
}
// 5. 业务路由配置类型
type AppRouteConfig = RouteConfig<AppRouteMeta>;
// 6. 路由表示例
const routes = [
{
path: '/',
name: 'Home',
component: 'HomeView',
meta: { title: '首页', icon: 'home', affix: true },
},
{
path: '/user',
name: 'User',
component: 'UserLayout',
meta: { title: '用户管理', icon: 'user', requiresAuth: true },
children: [
{
path: 'list',
name: 'UserList',
component: 'UserList',
meta: { title: '用户列表', icon: 'list' },
},
{
path: 'detail/:id',
name: 'UserDetail',
component: 'UserDetail',
meta: { title: '用户详情', hidden: true, activeMenu: '/user/list' },
},
],
},
{
path: '/about',
name: 'About',
component: 'AboutView',
meta: { title: '关于', icon: 'info' },
},
{
path: '/:pathMatch(.*)*',
redirect: '/404',
meta: { title: '404', hidden: true },
},
] as const;
// 7. 推导出所有路由名的联合类型
type AppRouteName = RouteNames<typeof routes>;
// => 'Home' | 'User' | 'UserList' | 'UserDetail' | 'About'
// 8. 编程式导航函数(类型安全)
interface RouteLocation {
name?: AppRouteName;
path?: string;
params?: Record<string, string | number>;
query?: Record<string, string | number>;
}
declare function push(to: RouteLocation): Promise<void>;
// 使用:路由名有类型提示和校验
function goUserDetail(id: number) {
push({ name: 'UserDetail', params: { id } });
}
RouteConfig<M>[]:children 类型就是 RouteConfig<M>[] 自身,形成递归引用,天然支持任意层级的嵌套路由,子路由和父路由享有相同的类型约束。RouteConfig<M = RouteMetaBase>:meta 字段的类型由泛型 M 决定,默认是基础类型,业务项目可以扩展自己的 meta 字段(如 affix、breadcrumb 等),类型跟着项目走。RouteNames<T>:用条件类型 + infer 递归遍历路由树,把所有层级的 name 字段收集起来形成联合类型,编程式导航时 name 参数自动提示所有可用路由名。as const 收窄字面量:路由表加 as const 后,name 字段从 string 收窄为字面量类型,RouteNames 才能推导出精确的联合类型,而非宽泛的 string。// 路由名联合类型推导
type Names = AppRouteName;
// => 'Home' | 'User' | 'UserList' | 'UserDetail' | 'About'
// 编程式导航时 name 只能是已知路由
// push({ name: 'UserDetai' }); // 错误:拼写错误直接被 TS 拦截
// meta 字段类型检查
const route: AppRouteConfig = {
path: '/test',
name: 'Test',
meta: {
title: '测试', // 必填,缺了会报错
affix: true, // 自定义字段可用
// unknownField: 123, // 错误:未知字段
},
};
// 子路由同样受类型约束
const nested: AppRouteConfig = {
path: '/parent',
name: 'Parent',
meta: { title: '父路由' },
children: [
{
path: 'child',
name: 'Child',
meta: { title: '子路由' },
children: [
{
path: 'grandchild',
name: 'Grandchild',
meta: { title: '孙子路由' },
}
]
}
]
};
// 三级嵌套路由的 name 也能被提取
type NestedNames = RouteNames<typeof [nested]>;
// => 'Parent' | 'Child' | 'Grandchild'