路由元信息类型推断
路由元信息类型推断
Vue Router 允许开发者为路由配置添加自定义元信息(meta),这些信息通常用于权限控制、页面标题设置等场景。随着 TypeScript 在 Vue 项目中的普及,如何为这些元信息提供类型支持成为提升开发体验的关键点。
基础类型定义
最简单的元信息类型可以通过接口直接定义:
interface RouteMeta {
requiresAuth: boolean
title: string
}
const router = createRouter({
routes: [
{
path: '/dashboard',
meta: {
requiresAuth: true,
title: '控制面板'
}
}
]
})
这种方式的缺点是类型定义与路由配置分离,当元信息结构变化时需要同步修改两处代码。
扩展 RouteMeta 接口
更推荐的做法是扩展 Vue Router 的 RouteMeta
接口:
// types/router.d.ts
import 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
requiresAuth: boolean
title: string
icon?: string
}
}
这样所有路由的元信息都会自动获得类型检查,且可以通过模块声明实现全局类型共享。
嵌套元信息结构
对于复杂的应用场景,元信息可能需要嵌套结构:
interface Permission {
roles: string[]
permissions: string[]
}
declare module 'vue-router' {
interface RouteMeta {
auth: {
required: boolean
redirect?: string
}
page: {
title: string
transition?: string
}
permission?: Permission
}
}
// 使用示例
const adminRoute = {
path: '/admin',
meta: {
auth: {
required: true,
redirect: '/login'
},
page: {
title: '管理后台',
transition: 'fade'
},
permission: {
roles: ['admin'],
permissions: ['user:manage']
}
}
}
基于泛型的类型推断
对于需要动态生成路由配置的场景,可以创建路由配置工厂函数:
function defineRoute<T extends RouteMeta>(config: {
path: string
meta: T
}): RouteRecordRaw {
return {
...config,
component: () => import(`@/views${config.path}.vue`)
}
}
const routes = [
defineRoute({
path: '/profile',
meta: {
requiresAuth: true,
title: '个人资料',
// 这里会检查是否缺少 requiredAuth 或 title
}
})
]
路由守卫中的类型推断
在导航守卫中也能获得完整的类型提示:
router.beforeEach((to) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
// to.meta.redirect 会有自动补全
return to.meta.auth?.redirect || '/login'
}
document.title = to.meta.title // 这里会报错,提示拼写错误
})
动态元信息合并
有时需要根据条件动态合并元信息:
function withAnalytics<T extends RouteMeta>(meta: T): T {
return {
...meta,
analytics: {
trackPageView: true,
category: meta.title
}
}
}
const routes = [
{
path: '/products',
meta: withAnalytics({
title: '产品列表',
requiresAuth: false
})
}
]
需要相应扩展 RouteMeta
接口来包含 analytics
属性。
类型安全的元信息访问器
可以创建工具函数来安全访问元信息:
function getRouteTitle(route: RouteLocationNormalized): string {
if (typeof route.meta.title !== 'string') {
throw new Error('Missing required title meta field')
}
return route.meta.title
}
// 或者使用类型谓词
function hasRequiredMeta(
meta: RouteMeta
): meta is RouteMeta & { title: string; requiresAuth: boolean } {
return typeof meta.title === 'string' && typeof meta.requiresAuth === 'boolean'
}
元信息与组合式 API
在 setup 函数中访问路由元信息:
import { useRoute } from 'vue-router'
export default {
setup() {
const route = useRoute()
watch(() => route.meta.title, (title) => {
document.title = title || '默认标题'
})
// 需要类型断言时
const analyticsMeta = route.meta as { analytics?: { enabled: boolean } }
}
}
高级模式匹配
对于动态路由,可以使用映射类型来定义元信息:
type DynamicRouteMeta = {
[key: string]: {
title: string
params: Record<string, string>
}
}
declare module 'vue-router' {
interface RouteMeta {
dynamic?: DynamicRouteMeta
}
}
// 使用示例
const routes = [{
path: '/user/:id',
meta: {
dynamic: {
user: {
title: '用户详情',
params: { id: '' } // 必须包含 id 参数
}
}
}
}]
元信息与路由配置分离
对于大型项目,可以将元信息单独管理:
// meta-configs.ts
export const metaConfigs = {
dashboard: {
requiresAuth: true,
title: '仪表盘',
icon: 'mdi-gauge'
},
settings: {
requiresAuth: true,
title: '系统设置',
permissionLevel: 2
}
} as const
// router.ts
const routes = [
{
path: '/dashboard',
meta: metaConfigs.dashboard
}
]
需要扩展 RouteMeta
来包含所有可能的元信息类型。
类型测试与验证
可以编写类型测试来确保元信息结构正确:
type AssertExtends<T, U extends T> = true
// 测试元信息类型
type TestMeta = AssertExtends<RouteMeta, {
title: string
requiresAuth: boolean
}>
与 Pinia 集成
当路由元信息需要与状态管理交互时:
export const useAuthStore = defineStore('auth', {
actions: {
checkPermission(route: RouteLocationNormalized) {
if (route.meta.requiresAuth && !this.isLoggedIn) {
throw new Error('Unauthorized')
}
// 可以访问扩展的 meta 类型
if (route.meta.permission?.roles.includes('admin')) {
// ...
}
}
}
})
自动生成类型文档
利用 TypeScript 类型可以自动生成元信息文档:
type MetaFieldDescription = {
[K in keyof RouteMeta]: {
type: string
required: boolean
description: string
}
}
const metaDocs: MetaFieldDescription = {
title: {
type: 'string',
required: true,
description: '页面标题'
},
requiresAuth: {
type: 'boolean',
required: false,
description: '是否需要认证'
}
}
多级路由类型合并
对于嵌套路由,子路由会继承父路由的元信息类型:
const routes = [
{
path: '/admin',
meta: { accessLevel: 2 },
children: [
{
path: 'users',
meta: {
// 必须包含 accessLevel
title: '用户管理'
}
}
]
}
]
可以通过类型工具实现部分覆盖:
type PartialRouteMeta = Partial<RouteMeta> & {
title: string // 确保 title 始终存在
}
运行时类型验证
虽然 TypeScript 在编译时提供类型检查,但有时需要运行时验证:
import { is } from 'typescript-is'
router.beforeEach((to) => {
if (!is<RouteMeta>(to.meta)) {
console.warn('Invalid route meta', to.meta)
}
// 或者使用 Zod 等验证库
const metaSchema = z.object({
title: z.string(),
requiresAuth: z.boolean().optional()
})
const result = metaSchema.safeParse(to.meta)
})
元信息类型演变
随着项目发展,元信息类型可能需要版本控制:
type LegacyMeta = {
needAuth?: boolean
pageTitle?: string
}
type CurrentMeta = {
requiresAuth: boolean
title: string
}
type RouteMeta = CurrentMeta | (LegacyMeta & {
__isLegacy: true
})
// 使用时需要类型守卫
function normalizeMeta(meta: RouteMeta): CurrentMeta {
if ('__isLegacy' in meta) {
return {
requiresAuth: meta.needAuth ?? false,
title: meta.pageTitle || '默认标题'
}
}
return meta
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn