Chapter 04

Vue Router 4 路由管理

从基础路由配置到路由守卫,掌握 Vue Router 4 的全部特性,构建优雅的单页应用导航系统

4.1 基础配置

Vue Router 4 是 Vue 3 的官方路由库,API 设计与 Vue 3 的 Composition API 深度整合。

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const router = createRouter({
  // createWebHistory:HTML5 History 模式(推荐,需要服务器配置)
  // createWebHashHistory:Hash 模式(URL 带 #,无需服务器配置)
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView          // 直接导入(首屏组件)
    },
    {
      path: '/about',
      name: 'about',
      // 懒加载:路由被访问时才下载 JS
      component: () => import('../views/AboutView.vue')
    },
    {
      path: '/users/:id',         // 动态路由参数
      name: 'user-detail',
      component: () => import('../views/UserDetail.vue'),
      props: true                   // 路由参数作为 props 传入
    },
    {
      path: '/:pathMatch(.*)*',    // 404 页面(匹配所有未知路径)
      name: 'not-found',
      component: () => import('../views/NotFound.vue')
    }
  ],
  // 滚动行为控制
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) return savedPosition  // 浏览器前进/后退时恢复位置
    if (to.hash) return { el: to.hash, behavior: 'smooth' }
    return { top: 0 }
  }
})

export default router
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')

4.2 动态路由与嵌套路由

const routes = [
  {
    path: '/dashboard',
    component: () => import('@/layouts/DashboardLayout.vue'),
    // 嵌套路由:DashboardLayout 中需要有 <RouterView />
    children: [
      {
        path: '',                // 默认子路由(/dashboard)
        component: () => import('@/views/DashboardHome.vue')
      },
      {
        path: 'profile',        // /dashboard/profile
        component: () => import('@/views/DashboardProfile.vue')
      },
      {
        path: 'settings/:tab?', // 可选参数(? 表示可省略)
        component: () => import('@/views/DashboardSettings.vue')
      }
    ]
  },
  {
    // 路由元信息:可以携带任意数据
    path: '/admin',
    meta: { requiresAuth: true, roles: ['admin'] },
    component: () => import('@/views/Admin.vue')
  }
]

4.3 路由守卫

路由守卫用于在路由导航时进行权限检查、数据预加载等操作。分为全局守卫、路由级守卫和组件内守卫:

import { useAuthStore } from '@/stores/auth'

// 全局前置守卫:每次导航前执行
router.beforeEach(async (to, from) => {
  const auth = useAuthStore()

  // 需要登录但未登录
  if (to.meta.requiresAuth && !auth.isLoggedIn) {
    return { name: 'login', query: { redirect: to.fullPath } }
  }

  // 检查角色权限
  if (to.meta.roles && !to.meta.roles.includes(auth.userRole)) {
    return { name: 'forbidden' }
  }

  // return undefined 或 true 表示放行
  // return false 取消导航
  // return { path: '/other' } 重定向
})

// 全局后置钩子(不影响导航,常用于更新页面标题)
router.afterEach((to) => {
  document.title = (to.meta.title as string) || '我的应用'
})
<script setup lang="ts">
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'

const isDirty = ref(false)

// 离开当前路由前(可用于提示保存)
onBeforeRouteLeave((to, from) => {
  if (isDirty.value) {
    const ok = window.confirm('有未保存的更改,确认离开?')
    if (!ok) return false
  }
})

// 路由参数变化时(同组件复用,如 /user/1 → /user/2)
onBeforeRouteUpdate(async (to, from) => {
  const userId = to.params.id
  await loadUser(userId)
})
</script>

4.4 组合式 API:useRouter 与 useRoute

<script setup lang="ts">
import { useRouter, useRoute } from 'vue-router'
import { computed, watch } from 'vue'

const router = useRouter()  // 路由器实例(导航方法)
const route  = useRoute()   // 当前路由信息(响应式)

// 读取路由信息
const userId = computed(() => route.params.id as string)
const tab    = computed(() => route.query.tab  as string || 'overview')

// 侦听路由变化
watch(() => route.params.id, async (newId) => {
  await loadUser(newId)
}, { immediate: true })

// 编程式导航
function goToUser(id: number) {
  router.push({ name: 'user-detail', params: { id } })
}

function updateFilter(filter: string) {
  // 更新查询参数而不添加历史记录
  router.replace({ query: { ...route.query, filter } })
}

function goBack() {
  router.back()   // 等同于 router.go(-1)
}
</script>

4.5 路由懒加载与代码分割

路由懒加载是 SPA 性能优化的关键手段,Vite 会将每个懒加载路由打包为独立 chunk:

const routes = [
  // 基础懒加载
  {
    path: '/heavy',
    component: () => import('@/views/HeavyView.vue')
  },
  // 命名 chunk(便于分析)
  {
    path: '/admin',
    component: () => import(/* webpackChunkName: "admin" */ '@/views/Admin.vue')
  },
  // 加载时显示 Loading 状态
  {
    path: '/dashboard',
    component: {
      // defineAsyncComponent 可以配置加载/错误组件
      component: defineAsyncComponent({
        loader: () => import('@/views/Dashboard.vue'),
        loadingComponent: LoadingSpinner,
        errorComponent: ErrorDisplay,
        delay: 200,        // 延迟显示 loading(避免闪烁)
        timeout: 10000     // 超时时间
      })
    }
  }
]
History 模式
使用 HTML5 History API,URL 格式为正常路径(/about),需要服务器配置将所有路径重定向到 index.html。
Hash 模式
URL 格式为 /#/about,不需要服务器配置,但 URL 不够美观,SEO 不友好。适合无法配置服务器的情况。
路由元信息 meta
在路由定义中通过 meta 字段附加任意数据(如 requiresAuth、title、roles),在守卫中通过 to.meta 访问。需要在 TypeScript 中通过模块扩展声明类型。
动态路由添加
router.addRoute() 可以在运行时动态添加路由,常用于根据用户权限动态注册路由的场景。
useRoute 的注意事项

useRoute() 返回的对象是响应式的,但其属性(如 route.params)本身是只读的。要侦听路由参数变化,使用 watch(() => route.params.id, handler) 而不是 watch(route.params, handler)(后者可能错过初始化)。