Type inference for route meta information
Type Inference for Route Meta Information
Vue Router allows developers to add custom meta information (meta) to route configurations, which is commonly used for scenarios such as permission control and page title settings. With the increasing adoption of TypeScript in Vue projects, providing type support for this meta information has become a key factor in improving the development experience.
Basic Type Definitions
The simplest meta information types can be defined directly using interfaces:
interface RouteMeta {
requiresAuth: boolean
title: string
}
const router = createRouter({
routes: [
{
path: '/dashboard',
meta: {
requiresAuth: true,
title: 'Dashboard'
}
}
]
})
The drawback of this approach is that the type definition is separated from the route configuration, requiring updates in both places when the meta information structure changes.
Extending the RouteMeta Interface
A more recommended approach is to extend Vue Router's RouteMeta
interface:
// types/router.d.ts
import 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
requiresAuth: boolean
title: string
icon?: string
}
}
This way, all route meta information automatically gains type checking, and global type sharing can be achieved through module declarations.
Nested Meta Information Structure
For complex scenarios, meta information may require a nested structure:
interface Permission {
roles: string[]
permissions: string[]
}
declare module 'vue-router' {
interface RouteMeta {
auth: {
required: boolean
redirect?: string
}
page: {
title: string
transition?: string
}
permission?: Permission
}
}
// Usage example
const adminRoute = {
path: '/admin',
meta: {
auth: {
required: true,
redirect: '/login'
},
page: {
title: 'Admin Panel',
transition: 'fade'
},
permission: {
roles: ['admin'],
permissions: ['user:manage']
}
}
}
Type Inference Based on Generics
For scenarios requiring dynamically generated route configurations, you can create a route configuration factory function:
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: 'Profile',
// This will check if requiredAuth or title is missing
}
})
]
Type Inference in Route Guards
Full type hints are also available in navigation guards:
router.beforeEach((to) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
// to.meta.redirect will have autocompletion
return to.meta.auth?.redirect || '/login'
}
document.title = to.meta.title // This will report an error for spelling mistakes
})
Dynamic Meta Information Merging
Sometimes, meta information needs to be dynamically merged based on conditions:
function withAnalytics<T extends RouteMeta>(meta: T): T {
return {
...meta,
analytics: {
trackPageView: true,
category: meta.title
}
}
}
const routes = [
{
path: '/products',
meta: withAnalytics({
title: 'Product List',
requiresAuth: false
})
}
]
The RouteMeta
interface needs to be extended to include the analytics
property.
Type-Safe Meta Information Accessors
Utility functions can be created to safely access meta information:
function getRouteTitle(route: RouteLocationNormalized): string {
if (typeof route.meta.title !== 'string') {
throw new Error('Missing required title meta field')
}
return route.meta.title
}
// Or use type predicates
function hasRequiredMeta(
meta: RouteMeta
): meta is RouteMeta & { title: string; requiresAuth: boolean } {
return typeof meta.title === 'string' && typeof meta.requiresAuth === 'boolean'
}
Meta Information and Composition API
Accessing route meta information in setup functions:
import { useRoute } from 'vue-router'
export default {
setup() {
const route = useRoute()
watch(() => route.meta.title, (title) => {
document.title = title || 'Default Title'
})
// Type assertion when needed
const analyticsMeta = route.meta as { analytics?: { enabled: boolean } }
}
}
Advanced Pattern Matching
For dynamic routes, mapped types can be used to define meta information:
type DynamicRouteMeta = {
[key: string]: {
title: string
params: Record<string, string>
}
}
declare module 'vue-router' {
interface RouteMeta {
dynamic?: DynamicRouteMeta
}
}
// Usage example
const routes = [{
path: '/user/:id',
meta: {
dynamic: {
user: {
title: 'User Details',
params: { id: '' } // Must include the id parameter
}
}
}
}]
Separating Meta Information from Route Configuration
For large projects, meta information can be managed separately:
// meta-configs.ts
export const metaConfigs = {
dashboard: {
requiresAuth: true,
title: 'Dashboard',
icon: 'mdi-gauge'
},
settings: {
requiresAuth: true,
title: 'System Settings',
permissionLevel: 2
}
} as const
// router.ts
const routes = [
{
path: '/dashboard',
meta: metaConfigs.dashboard
}
]
The RouteMeta
interface needs to be extended to include all possible meta information types.
Type Testing and Validation
Type tests can be written to ensure the meta information structure is correct:
type AssertExtends<T, U extends T> = true
// Test meta information types
type TestMeta = AssertExtends<RouteMeta, {
title: string
requiresAuth: boolean
}>
Integration with Pinia
When route meta information needs to interact with state management:
export const useAuthStore = defineStore('auth', {
actions: {
checkPermission(route: RouteLocationNormalized) {
if (route.meta.requiresAuth && !this.isLoggedIn) {
throw new Error('Unauthorized')
}
// Can access extended meta types
if (route.meta.permission?.roles.includes('admin')) {
// ...
}
}
}
})
Automatically Generating Type Documentation
TypeScript types can be used to automatically generate meta information documentation:
type MetaFieldDescription = {
[K in keyof RouteMeta]: {
type: string
required: boolean
description: string
}
}
const metaDocs: MetaFieldDescription = {
title: {
type: 'string',
required: true,
description: 'Page title'
},
requiresAuth: {
type: 'boolean',
required: false,
description: 'Whether authentication is required'
}
}
Multi-Level Route Type Merging
For nested routes, child routes inherit the meta information types of their parent routes:
const routes = [
{
path: '/admin',
meta: { accessLevel: 2 },
children: [
{
path: 'users',
meta: {
// Must include accessLevel
title: 'User Management'
}
}
]
}
]
Partial overriding can be achieved using type utilities:
type PartialRouteMeta = Partial<RouteMeta> & {
title: string // Ensure title always exists
}
Runtime Type Validation
While TypeScript provides type checking at compile time, runtime validation is sometimes needed:
import { is } from 'typescript-is'
router.beforeEach((to) => {
if (!is<RouteMeta>(to.meta)) {
console.warn('Invalid route meta', to.meta)
}
// Or use validation libraries like Zod
const metaSchema = z.object({
title: z.string(),
requiresAuth: z.boolean().optional()
})
const result = metaSchema.safeParse(to.meta)
})
Evolution of Meta Information Types
As projects evolve, meta information types may require version control:
type LegacyMeta = {
needAuth?: boolean
pageTitle?: string
}
type CurrentMeta = {
requiresAuth: boolean
title: string
}
type RouteMeta = CurrentMeta | (LegacyMeta & {
__isLegacy: true
})
// Type guards are needed when using
function normalizeMeta(meta: RouteMeta): CurrentMeta {
if ('__isLegacy' in meta) {
return {
requiresAuth: meta.needAuth ?? false,
title: meta.pageTitle || 'Default Title'
}
}
return meta
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn