Skip to content

Vue Router 动态路由

动态路由是Vue Router的核心特性之一,允许我们创建灵活的路由模式,支持参数传递、嵌套路由和运行时路由管理。

🔧 路径参数详解

1. 基础参数匹配

javascript
const routes = [
  // 基础参数
  { path: '/user/:id', component: User },
  
  // 多个参数
  { path: '/user/:id/post/:postId', component: Post },
  
  // 可选参数
  { path: '/user/:id?', component: User },
  
  // 参数约束(正则表达式)
  { path: '/user/:id(\\d+)', component: User },
  
  // 可重复参数
  { path: '/user/:ids+', component: Users },
  
  // 通配符
  { path: '/user/*', component: UserCatchAll },
  
  // 命名参数
  { 
    path: '/user/:id', 
    name: 'user',
    component: User 
  }
]

// 在组件中访问参数
export default {
  name: 'User',
  created() {
    console.log('用户ID:', this.$route.params.id)
    console.log('查询参数:', this.$route.query)
    console.log('完整路径:', this.$route.fullPath)
  },
  
  // 监听路由参数变化
  watch: {
    '$route'(to, from) {
      console.log('路由变化:', from.params.id, '->', to.params.id)
      this.fetchUser(to.params.id)
    }
  },
  
  methods: {
    async fetchUser(id) {
      try {
        this.user = await api.getUser(id)
      } catch (error) {
        console.error('获取用户失败:', error)
      }
    }
  }
}

2. 高级参数模式

javascript
const routes = [
  // 自定义正则
  {
    path: '/order/:orderId([A-Z]{2}\\d{6})',
    component: Order,
    meta: { title: '订单详情' }
  },
  
  // 多个可选参数
  {
    path: '/search/:category?/:keyword?',
    component: Search,
    props: route => ({
      category: route.params.category || 'all',
      keyword: route.params.keyword || '',
      page: parseInt(route.query.page) || 1
    })
  },
  
  // 数组参数
  {
    path: '/tags/:tags*',
    component: TagList,
    props: route => ({
      tags: route.params.tags ? route.params.tags.split('/') : []
    })
  },
  
  // 敏感参数匹配
  {
    path: '/Case/:id',
    component: CaseSensitive,
    caseSensitive: true
  },
  
  // 严格模式
  {
    path: '/strict/',
    component: Strict,
    strict: true
  }
]

3. 参数验证与转换

javascript
// 参数验证守卫
function validateParams(to, from, next) {
  const { id } = to.params
  
  // 验证ID格式
  if (!/^\d+$/.test(id)) {
    next({ name: 'NotFound' })
    return
  }
  
  // 验证ID范围
  const numId = parseInt(id)
  if (numId < 1 || numId > 999999) {
    next({ name: 'InvalidId' })
    return
  }
  
  next()
}

const routes = [
  {
    path: '/user/:id',
    component: User,
    beforeEnter: validateParams,
    props: route => ({
      userId: parseInt(route.params.id),
      tab: route.query.tab || 'profile'
    })
  }
]

// 组件内参数处理
export default {
  name: 'User',
  props: ['userId', 'tab'],
  
  async created() {
    await this.loadUser()
  },
  
  async beforeRouteUpdate(to, from) {
    // 参数变化时重新加载
    if (to.params.id !== from.params.id) {
      this.userId = parseInt(to.params.id)
      await this.loadUser()
    }
  },
  
  methods: {
    async loadUser() {
      this.loading = true
      try {
        this.user = await api.getUser(this.userId)
      } catch (error) {
        if (error.status === 404) {
          this.$router.replace({ name: 'UserNotFound' })
        } else {
          this.error = error.message
        }
      } finally {
        this.loading = false
      }
    }
  }
}

🏗️ 嵌套路由实现

1. 基础嵌套路由

javascript
const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      // 空路径表示默认子路由
      { path: '', component: UserHome },
      
      // 相对路径
      { path: 'profile', component: UserProfile },
      { path: 'posts', component: UserPosts },
      { path: 'settings', component: UserSettings },
      
      // 绝对路径
      { path: '/user/:id/admin', component: UserAdmin },
      
      // 嵌套参数
      {
        path: 'post/:postId',
        component: UserPost,
        props: true
      }
    ]
  }
]

// User组件模板
<template>
  <div class="user">
    <h2>用户 {{ $route.params.id }}</h2>
    <nav>
      <router-link :to="`/user/${$route.params.id}`">首页</router-link>
      <router-link :to="`/user/${$route.params.id}/profile`">资料</router-link>
      <router-link :to="`/user/${$route.params.id}/posts`">文章</router-link>
    </nav>
    
    <!-- 子路由出口 -->
    <router-view></router-view>
  </div>
</template>

2. 命名视图嵌套

javascript
const routes = [
  {
    path: '/dashboard',
    components: {
      default: Dashboard,
      sidebar: Sidebar,
      header: Header
    },
    children: [
      {
        path: 'analytics',
        components: {
          default: Analytics,
          sidebar: AnalyticsSidebar
        }
      },
      {
        path: 'reports',
        components: {
          default: Reports,
          sidebar: ReportsSidebar,
          toolbar: ReportsToolbar
        }
      }
    ]
  }
]

// Dashboard组件模板
<template>
  <div class="dashboard">
    <header>
      <router-view name="header"></router-view>
    </header>
    
    <aside>
      <router-view name="sidebar"></router-view>
    </aside>
    
    <main>
      <router-view name="toolbar"></router-view>
      <router-view></router-view>
    </main>
  </div>
</template>

3. 动态嵌套路由

javascript
// 动态添加子路由
class DynamicRouteManager {
  constructor(router) {
    this.router = router
    this.moduleRoutes = new Map()
  }
  
  // 添加模块路由
  addModuleRoutes(moduleId, routes) {
    const parentRoute = {
      path: `/module/${moduleId}`,
      component: ModuleLayout,
      children: routes
    }
    
    this.router.addRoute(parentRoute)
    this.moduleRoutes.set(moduleId, parentRoute)
  }
  
  // 移除模块路由
  removeModuleRoutes(moduleId) {
    const route = this.moduleRoutes.get(moduleId)
    if (route && route.name) {
      this.router.removeRoute(route.name)
      this.moduleRoutes.delete(moduleId)
    }
  }
  
  // 动态加载模块
  async loadModule(moduleId) {
    try {
      const module = await import(`@/modules/${moduleId}/routes.js`)
      this.addModuleRoutes(moduleId, module.default)
      return true
    } catch (error) {
      console.error(`加载模块 ${moduleId} 失败:`, error)
      return false
    }
  }
}

const routeManager = new DynamicRouteManager(router)

// 使用示例
router.beforeEach(async (to, from) => {
  const moduleMatch = to.path.match(/^\/module\/([^\/]+)/)
  if (moduleMatch) {
    const moduleId = moduleMatch[1]
    if (!routeManager.moduleRoutes.has(moduleId)) {
      const loaded = await routeManager.loadModule(moduleId)
      if (!loaded) {
        return { name: 'ModuleNotFound' }
      }
    }
  }
})

🚀 路由懒加载优化

1. 基础懒加载

javascript
const routes = [
  {
    path: '/home',
    component: () => import('@/views/Home.vue')
  },
  
  // 命名chunk
  {
    path: '/about',
    component: () => import(/* webpackChunkName: "about" */ '@/views/About.vue')
  },
  
  // 预加载
  {
    path: '/contact',
    component: () => import(/* webpackPreload: true */ '@/views/Contact.vue')
  },
  
  // 预获取
  {
    path: '/help',
    component: () => import(/* webpackPrefetch: true */ '@/views/Help.vue')
  }
]

2. 条件懒加载

javascript
// 根据权限懒加载
function createLazyRoute(path, importFn, roles = []) {
  return {
    path,
    component: () => {
      // 检查权限
      if (roles.length > 0 && !hasAnyRole(roles)) {
        return import('@/views/Forbidden.vue')
      }
      return importFn()
    }
  }
}

const routes = [
  createLazyRoute('/admin', () => import('@/views/Admin.vue'), ['admin']),
  createLazyRoute('/user', () => import('@/views/User.vue'), ['user', 'admin'])
]

// 环境条件懒加载
const routes = [
  {
    path: '/debug',
    component: process.env.NODE_ENV === 'development'
      ? () => import('@/views/Debug.vue')
      : () => import('@/views/NotFound.vue')
  }
]

3. 智能预加载

javascript
class SmartPreloader {
  constructor(router) {
    this.router = router
    this.preloadedRoutes = new Set()
    this.preloadQueue = []
    this.isPreloading = false
  }
  
  // 预加载路由
  async preloadRoute(routeName) {
    if (this.preloadedRoutes.has(routeName)) {
      return
    }
    
    const route = this.router.resolve({ name: routeName })
    if (route.matched.length > 0) {
      const component = route.matched[route.matched.length - 1].components?.default
      
      if (typeof component === 'function') {
        try {
          await component()
          this.preloadedRoutes.add(routeName)
        } catch (error) {
          console.error(`预加载路由 ${routeName} 失败:`, error)
        }
      }
    }
  }
  
  // 批量预加载
  async batchPreload(routeNames) {
    this.preloadQueue.push(...routeNames)
    
    if (!this.isPreloading) {
      this.isPreloading = true
      await this.processPreloadQueue()
      this.isPreloading = false
    }
  }
  
  async processPreloadQueue() {
    while (this.preloadQueue.length > 0) {
      const routeName = this.preloadQueue.shift()
      await this.preloadRoute(routeName)
      
      // 避免阻塞主线程
      await new Promise(resolve => setTimeout(resolve, 10))
    }
  }
  
  // 基于用户行为预加载
  onRouteEnter(to) {
    const preloadMap = {
      'home': ['about', 'contact'],
      'user-profile': ['user-settings', 'user-posts'],
      'product-list': ['product-detail']
    }
    
    const routesToPreload = preloadMap[to.name]
    if (routesToPreload) {
      // 延迟预加载,避免影响当前页面加载
      setTimeout(() => {
        this.batchPreload(routesToPreload)
      }, 1000)
    }
  }
}

const preloader = new SmartPreloader(router)

router.afterEach((to) => {
  preloader.onRouteEnter(to)
})

🎯 路由元信息与数据获取

1. 路由元信息

javascript
const routes = [
  {
    path: '/admin',
    component: Admin,
    meta: {
      requiresAuth: true,
      roles: ['admin'],
      title: '管理后台',
      icon: 'admin',
      breadcrumb: ['首页', '管理后台'],
      keepAlive: true,
      transition: 'slide-left'
    }
  }
]

// 全局处理元信息
router.beforeEach((to, from) => {
  // 设置页面标题
  if (to.meta.title) {
    document.title = to.meta.title
  }
  
  // 设置面包屑
  if (to.meta.breadcrumb) {
    store.commit('setBreadcrumb', to.meta.breadcrumb)
  }
})

2. 路由数据获取

javascript
// 导航前获取数据
export default {
  name: 'Post',
  data() {
    return {
      post: null,
      loading: false,
      error: null
    }
  },
  
  async beforeRouteEnter(to, from, next) {
    try {
      const post = await fetchPost(to.params.id)
      next(vm => {
        vm.post = post
      })
    } catch (error) {
      next({ name: 'PostNotFound' })
    }
  },
  
  async beforeRouteUpdate(to, from) {
    if (to.params.id !== from.params.id) {
      this.loading = true
      try {
        this.post = await fetchPost(to.params.id)
      } catch (error) {
        this.error = error.message
      } finally {
        this.loading = false
      }
    }
  }
}

// 导航后获取数据
export default {
  name: 'Post',
  data() {
    return {
      post: null,
      loading: false,
      error: null
    }
  },
  
  async created() {
    await this.fetchData()
  },
  
  async beforeRouteUpdate(to, from) {
    await this.fetchData()
  },
  
  methods: {
    async fetchData() {
      this.loading = true
      this.error = null
      
      try {
        this.post = await fetchPost(this.$route.params.id)
      } catch (error) {
        this.error = error.message
        if (error.status === 404) {
          this.$router.replace({ name: 'PostNotFound' })
        }
      } finally {
        this.loading = false
      }
    }
  }
}

🔍 路由调试与优化

1. 路由调试工具

javascript
// 路由调试插件
const routeDebugger = {
  install(app) {
    if (process.env.NODE_ENV === 'development') {
      const router = app.config.globalProperties.$router
      
      router.beforeEach((to, from) => {
        console.group(`🚀 路由导航: ${from.path} -> ${to.path}`)
        console.log('目标路由:', to)
        console.log('来源路由:', from)
        console.log('匹配的路由记录:', to.matched)
        console.groupEnd()
      })
      
      router.afterEach((to, from, failure) => {
        if (failure) {
          console.error('❌ 路由导航失败:', failure)
        } else {
          console.log('✅ 路由导航成功')
        }
      })
    }
  }
}

app.use(routeDebugger)

2. 性能监控

javascript
// 路由性能监控
class RoutePerformanceMonitor {
  constructor() {
    this.navigationTimes = new Map()
  }
  
  startNavigation(to) {
    this.navigationTimes.set(to.fullPath, {
      start: performance.now(),
      route: to
    })
  }
  
  endNavigation(to) {
    const timing = this.navigationTimes.get(to.fullPath)
    if (timing) {
      const duration = performance.now() - timing.start
      console.log(`路由 ${to.path} 导航耗时: ${duration.toFixed(2)}ms`)
      
      // 发送性能数据
      if (duration > 1000) {
        this.reportSlowNavigation(to, duration)
      }
    }
  }
  
  reportSlowNavigation(route, duration) {
    // 发送到监控系统
    analytics.track('slow_navigation', {
      path: route.path,
      duration,
      timestamp: Date.now()
    })
  }
}

const performanceMonitor = new RoutePerformanceMonitor()

router.beforeEach((to) => {
  performanceMonitor.startNavigation(to)
})

router.afterEach((to) => {
  performanceMonitor.endNavigation(to)
})

动态路由是构建现代单页应用的基础,通过合理使用路径参数、嵌套路由和懒加载等特性,可以创建灵活高效的路由系统。