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> 有什么区别?
<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 3 | Vue Router 4 |
|---|---|---|
| API | Options | Composition + Options |
| 导航守卫 | beforeRouteEnter next() 回调 | 直接 return 目标或 false |
| TypeScript | 需手动类型声明 | 原生类型支持 |
| SSR | 手动集成 | Nuxt 3 原生支持 |
| 组合式 API | 不支持 | useRoute / useRouter |
生产环境中,重点关注导航守卫的权限校验、路由元信息的页面标题/面包屑管理,以及懒加载的性能优化。对于 SEO 敏感的项目,建议配合 SSR 或预渲染方案。
相关阅读
- Vue 详细说明 — Vue 核心概念
- Vue Composition API 完全指南 — Composition API 使用模式
- Pinia 状态管理指南 — 全局状态与路由结合
- Nuxt.js 完全指南 — 文件系统路由与 SSR
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。