Vue Router 完全指南:v4 路由模式、导航守卫、动态路由、懒加载与生产实践

Vue Router 4 深度实践:声明式路由与 Hash/History 模式、嵌套路由、动态路由匹配、导航守卫(全局/路由独享/组件内)、路由元信息、滚动行为、懒加载与代码分割、以及与 Vue 3 Composition API 的结合使用。

Vue Router 是 Vue.js 官方路由库,与 Vue 核心深度集成。从 Vue Router 3(Vue 2)到 Vue Router 4(Vue 3),API 全面拥抱 Composition API,同时保持与 Options API 的兼容。本文覆盖从基础配置到生产级导航守卫策略的完整实践。


一、路由基础配置

1.1 创建 Router(v4 推荐写法)

// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home,
    },
    {
      path: '/about',
      name: 'About',
      component: () => import('../views/About.vue'),  // 懒加载
    },
    {
      path: '/users/:id',
      name: 'UserProfile',
      component: () => import('../views/UserProfile.vue'),
      props: true,  // 将路由参数作为 props 传递
    },
  ],
})

export default router

1.2 在应用中使用

// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')
<!-- App.vue -->
<template>
  <nav>
    <RouterLink to="/">Home</RouterLink>
    <RouterLink to="/about">About</RouterLink>
  </nav>
  <main>
    <RouterView />  <!-- 路由组件渲染位置 -->
  </main>
</template>

1.3 Hash vs History 模式

模式用法URL需服务器配置适用
createWebHistory()History API/users/123✅ 需回退生产环境
createWebHashHistory()Hash/#/users/123❌ 无需静态托管、Electron
createMemoryHistory()内存不可见❌SSR、测试

二、嵌套路由与命名路由

2.1 嵌套结构

const routes = [
  {
    path: '/dashboard',
    component: DashboardLayout,
    children: [
      { path: '', name: 'DashboardHome', component: DashboardHome },           // /dashboard
      { path: 'analytics', name: 'Analytics', component: Analytics },          // /dashboard/analytics
      { path: 'settings', name: 'Settings', component: Settings },             // /dashboard/settings
    ],
  },
]
<!-- DashboardLayout.vue -->
<template>
  <div class="dashboard">
    <Sidebar />
    <div class="content">
      <RouterView />  <!-- 渲染子路由组件 -->
    </div>
  </div>
</template>

2.2 命名视图(多 <RouterView>)

const routes = [
  {
    path: '/',
    components: {
      default: Home,
      sidebar: Sidebar,     // <RouterView name="sidebar" />
      footer: Footer,       // <RouterView name="footer" />
    },
  },
]

三、动态路由与匹配

3.1 路径参数

const routes = [
  // /users/123 → { id: '123' }
  { path: '/users/:id', component: UserProfile, props: true },

  // /posts/2024/08/article-slug → { year: '2024', month: '08', slug: 'article-slug' }
  { path: '/posts/:year/:month/:slug', component: PostDetail },

  // 可选参数(?)
  { path: '/users/:id?', component: UserProfile },  // /users 和 /users/123 都匹配

  // 正则限制
  { path: '/users/:id(\\d+)', component: UserById },  // 只允许数字
]

3.2 在组件中读取参数

<script setup>
import { useRoute } from 'vue-router'
import { watch } from 'vue'

const route = useRoute()

// 读取参数
console.log(route.params.id)

// 读取查询参数 /search?q=vue → { q: 'vue' }
console.log(route.query.q)

// 读取 hash
console.log(route.hash)

// 监听参数变化(组件复用时不会重新挂载)
watch(() => route.params.id, (newId) => {
  fetchUser(newId)
})
</script>

3.3 编程式导航

import { useRouter } from 'vue-router'

const router = useRouter()

// 字符串路径
router.push('/users/123')

// 带查询参数
router.push({ path: '/search', query: { q: 'vue' } })

// 命名路由 + 参数
router.push({ name: 'UserProfile', params: { id: '123' } })

// 替换当前历史(不增加记录)
router.replace('/home')

// 后退
router.back()
router.go(-1)

四、导航守卫

4.1 全局前置守卫(鉴权)

// router/index.ts
router.beforeEach(async (to, from) => {
  const authStore = useAuthStore()

  // 需要登录但未登录 → 跳转登录
  if (to.meta.requiresAuth && !authStore.isLoggedIn) {
    return { name: 'Login', query: { redirect: to.fullPath } }
  }

  // 需要管理员权限
  if (to.meta.requiresAdmin && authStore.user?.role !== 'admin') {
    return { name: 'Unauthorized' }
  }

  // 已登录访问登录页 → 跳转首页
  if (to.name === 'Login' && authStore.isLoggedIn) {
    return { name: 'Home' }
  }
})

4.2 路由独享守卫

const routes = [
  {
    path: '/admin',
    component: AdminPanel,
    beforeEnter: (to, from) => {
      const auth = useAuthStore()
      if (!auth.isAdmin) return { name: 'Unauthorized' }
    },
  },
]

4.3 组件内守卫

<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

// 离开守卫(如未保存表单时确认)
onBeforeRouteLeave((to, from) => {
  const answer = window.confirm('You have unsaved changes. Leave?')
  if (!answer) return false  // 阻止导航
})

// 组件复用时更新(如 /users/1 → /users/2)
onBeforeRouteUpdate((to, from) => {
  // 复用组件,只需更新数据
  userId.value = to.params.id
  fetchUserData(to.params.id)
})
</script>

4.4 守卫执行顺序

导航触发
  ↓
beforeLeave(组件内)
  ↓
beforeEach(全局)
  ↓
beforeEnter(路由独享)
  ↓
beforeRouteEnter(组件内)
  ↓
路由解析
  ↓
全局 afterEach

五、滚动行为与过渡

5.1 滚动恢复

const router = createRouter({
  history: createWebHistory(),
  routes,
  scrollBehavior(to, from, savedPosition) {
    // 返回历史位置(浏览器前进/后退)
    if (savedPosition) {
      return savedPosition
    }

    // hash 锚点跳转
    if (to.hash) {
      return { el: to.hash, behavior: 'smooth' }
    }

    // 默认滚动到顶部
    return { top: 0, left: 0 }
  },
})

5.2 路由过渡动画

<template>
  <RouterView v-slot="{ Component }">
    <Transition name="fade" mode="out-in">
      <component :is="Component" />
    </Transition>
  </RouterView>
</template>

<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

六、懒加载与代码分割

const routes = [
  {
    path: '/dashboard',
    component: () => import('../views/Dashboard.vue'),
    // Webpack chunk name(Vite 需配置)
    // component: () => import(/* webpackChunkName: "dashboard" */ '../views/Dashboard.vue'),
  },
]

预加载:

<script setup>
// 鼠标悬停时预加载
const prefetchDashboard = () => {
  import('../views/Dashboard.vue')
}
</script>

<template>
  <RouterLink to="/dashboard" @mouseenter="prefetchDashboard">
    Dashboard
  </RouterLink>
</template>

常见问题(FAQ)

<RouterLink> 使用客户端导航(不刷新页面),<a> 触发整页刷新。外部链接用 <a>,内部路由用 <RouterLink>。

路由参数变化组件不复用怎么办?

添加 :key 强制重新挂载:<RouterView :key="$route.fullPath" />,但会丢失组件状态。

如何处理 404?

{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }

七、路由元信息与动态权限

7.1 路由元信息(Meta)

路由元信息用于存储与路由相关的配置数据,如页面标题、是否需要认证、面包屑导航等:

const routes = [
  {
    path: '/admin',
    component: AdminLayout,
    meta: {
      requiresAuth: true,
      requiresAdmin: true,
      title: '管理后台',
      breadcrumb: [
        { label: '首页', path: '/' },
        { label: '管理后台', path: '/admin' }
      ]
    },
    children: [
      {
        path: 'users',
        component: UserManagement,
        meta: {
          title: '用户管理',
          breadcrumb: [
            { label: '首页', path: '/' },
            { label: '管理后台', path: '/admin' },
            { label: '用户管理', path: '/admin/users' }
          ]
        }
      }
    ]
  }
]

7.2 动态标题与面包屑

// 全局后置守卫:设置页面标题
router.afterEach((to) => {
  const title = to.meta.title as string
  if (title) {
    document.title = `${title} | My App`
  }
})

// 面包屑组件
<script setup>
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()

const breadcrumbs = computed(() => {
  const matched = route.matched.filter(r => r.meta?.breadcrumb)
  return matched.flatMap(r => r.meta.breadcrumb as BreadcrumbItem[])
})
</script>

八、TypeScript 类型安全路由

在大型项目中,类型安全的路由定义可以避免拼写错误和参数类型不匹配:

// 定义路由名称枚举
export enum RouteNames {
  Home = 'Home',
  UserProfile = 'UserProfile',
  Dashboard = 'Dashboard',
  NotFound = 'NotFound'
}

// 定义路由参数类型
export interface RouteParams {
  [RouteNames.UserProfile]: { id: string }
  [RouteNames.Dashboard]: { tab?: string }
}

// 类型安全的 router.push
export function push<K extends keyof RouteParams>(
  name: K,
  params: RouteParams[K]
) {
  return router.push({ name, params })
}

// 使用
push(RouteNames.UserProfile, { id: '123' })  // ✅ 类型安全
push(RouteNames.UserProfile, { id: 123 })    // ❌ 类型错误

九、SSR 与 Nuxt.js 路由

在 Nuxt.js 中,路由基于文件系统自动生成,无需手动配置:

pages/
├── index.vue           # / 首页
├── about.vue           # /about
├── users/
│   ├── index.vue       # /users 列表页
│   └── [id].vue        # /users/:id 详情页(动态路由)
├── [...slug].vue       # /a/b/c 全文匹配(404 处理)
└── admin/
    └── dashboard.vue   # /admin/dashboard

Nuxt 还提供导航守卫的约定式写法:

<script setup>
// 页面级守卫
definePageMeta({
  middleware: 'auth',  // 使用中间件
  requiresAuth: true
})

// useRoute / useRouter 与 Vue Router 一致
const route = useRoute()
const router = useRouter()
</script>

总结

Vue Router 4 在 Vue 3 时代焕然一新:

特性Vue Router 3Vue Router 4
APIOptionsComposition + Options
导航守卫beforeRouteEnter next() 回调直接 return 目标或 false
TypeScript需手动类型声明原生类型支持
SSR手动集成Nuxt 3 原生支持
组合式 API不支持useRoute / useRouter

生产环境中,重点关注导航守卫的权限校验、路由元信息的页面标题/面包屑管理,以及懒加载的性能优化。对于 SEO 敏感的项目,建议配合 SSR 或预渲染方案。

相关阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「frontend」更多文章

  1. GraphQL BFF 与微前端:多前端团队的 Schema 分片与协作模式
  2. 前端 GraphQL 客户端集成:Apollo Client、Relay 与 urql 选型
  3. 前端性能调试实战:从首屏加载到运行时瓶颈