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 }

相关阅读

继续阅读

探索更多技术文章

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

全部文章 返回首页

「frontend」更多文章