阿里云主机折上折
  • 微信号
Current Site:Index > Type inference for route meta information

Type inference for route meta information

Author:Chuan Chen 阅读数:64472人阅读 分类: Vue.js

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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.