📖 文章概述
Vue Router 4 是 Vue 3 官方路由管理库,提供了强大而灵活的路由解决方案。本文深入讲解其核心特性、最佳实践和常见模式。
🎯 核心概念速览
什么是路由?
路由是 SPA(单页应用)的核心,用于管理应用的不同视图状态和导航流程。
// 传统多页应用:每个页面是独立的 HTML 文件
// index.html → /about.html → /contact.html
// SPA 应用:单个 HTML,通过路由切换组件
// / → /about → /contact (同一个 HTML,不同组件)
Vue Router 4 的三大改进
| 特性 | Vue Router 3 | Vue Router 4 |
|---|---|---|
| Vue 版本 | Vue 2 | Vue 3+ |
| 大小 | ~64KB | ~35KB |
| TypeScript | 部分支持 | 完整支持 |
| 组合式 API | ✗ | ✅ |
| 性能 | 良好 | 更好 |
🚀 快速开始
1. 安装和基础配置
npm install vue-router@4
创建路由配置文件
// src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Home from '../pages/Home.vue'
import About from '../pages/About.vue'
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
在 Vue 应用中使用
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
应用模板
<!-- App.vue -->
<template>
<div id="app">
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</nav>
<!-- 渲染当前路由组件 -->
<router-view />
</div>
</template>
🔀 高级路由配置
2. 动态路由参数
// 配置动态路由
{
path: '/user/:id',
name: 'UserProfile',
component: UserProfile
}
// 访问参数
import { useRoute } from 'vue-router'
export default {
setup() {
const route = useRoute()
// 获取路由参数
const userId = route.params.id
const query = route.query.tab // 查询字符串
return { userId, query }
}
}
3. 嵌套路由(子路由)
{
path: '/dashboard',
component: Dashboard,
children: [
{
path: 'profile', // 完整路径:/dashboard/profile
component: Profile
},
{
path: 'settings', // 完整路径:/dashboard/settings
component: Settings
},
{
path: '', // 完整路径:/dashboard
component: DashboardHome
}
]
}
渲染嵌套组件:
<!-- Dashboard.vue -->
<template>
<div class="dashboard">
<aside>
<nav>
<router-link to="/dashboard">Home</router-link>
<router-link to="/dashboard/profile">Profile</router-link>
<router-link to="/dashboard/settings">Settings</router-link>
</nav>
</aside>
<main>
<!-- 渲染子路由组件 -->
<router-view />
</main>
</div>
</template>
4. 命名视图(多个 router-view)
{
path: '/layout',
component: Layout,
children: [
{
path: 'dashboard',
components: {
default: Dashboard,
sidebar: Sidebar,
header: Header
}
}
]
}
<!-- Layout.vue -->
<template>
<div class="layout">
<router-view name="header" />
<router-view name="sidebar" />
<router-view /> <!-- default view -->
</div>
</template>
5. 路由懒加载
// 方式 1:使用 ES6 动态导入
{
path: '/analytics',
component: () => import('../pages/Analytics.vue')
}
// 方式 2:带注释的 webpack 分割
{
path: '/report',
component: () => import(/* webpackChunkName: "report" */ '../pages/Report.vue')
}
// 方式 3:带加载状态
import { defineAsyncComponent } from 'vue'
{
path: '/admin',
component: defineAsyncComponent({
loader: () => import('../pages/Admin.vue'),
loadingComponent: LoadingSpinner,
delay: 200,
timeout: 10000,
errorComponent: ErrorComponent,
onError: (error, retry, fail, attempts) => {
if (attempts <= 3) {
setTimeout(() => retry(), 1000)
} else {
fail()
}
}
})
}
🛡️ 导航守卫(Navigation Guards)
导航守卫是路由跳转时的拦截钩子,用于权限验证、页面跳转前处理等。
6. 全局守卫
// 全局前置守卫
router.beforeEach((to, from, next) => {
console.log(`从 ${from.path} 导航到 ${to.path}`)
// 权限检查
if (to.meta.requiresAuth) {
const isAuthenticated = !!localStorage.getItem('token')
if (isAuthenticated) {
next()
} else {
next('/login') // 重定向到登录页
}
} else {
next()
}
})
// 全局解析守卫(在所有守卫和异步路由组件完成后调用)
router.beforeResolve(async (to, from) => {
// 预加载数据
if (to.meta.requiresData) {
try {
await fetchData(to.params.id)
} catch (error) {
return false // 取消导航
}
}
})
// 全局后置钩子(不能改变导航结果)
router.afterEach((to, from) => {
// 更新页面标题
document.title = to.meta.title || 'Default Title'
// 页面统计
trackPageView(to.path)
})
7. 路由级别守卫
{
path: '/admin',
component: Admin,
meta: {
requiresAuth: true,
title: 'Admin Panel'
},
beforeEnter: (to, from, next) => {
// 仅在进入此路由时执行
const isAdmin = checkAdminPermission()
if (isAdmin) {
next()
} else {
next('/unauthorized')
}
}
}
8. 组件级别守卫
<script setup lang="ts">
import { onBeforeRouteUpdate, useRoute } from 'vue-router'
// 在当前组件内导航时调用
onBeforeRouteUpdate(async (to, from) => {
if (to.params.id !== from.params.id) {
// 重新加载数据
const data = await fetchUserData(to.params.id)
// 处理数据...
}
})
// 离开此路由时调用
onBeforeRouteLeave((to, from) => {
if (unsavedChanges.value) {
return confirm('有未保存的更改,确定离开吗?')
}
})
</script>
📊 实战:完整身份验证示例
// src/router/guards.ts
import { Router } from 'vue-router'
import { useAuthStore } from '../stores/auth'
export function setupAuthGuards(router: Router) {
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore()
// 需要认证的路由
if (to.meta.requiresAuth) {
if (authStore.isLoggedIn) {
// 检查权限
if (to.meta.roles && !to.meta.roles.includes(authStore.user.role)) {
next('/forbidden')
return
}
next()
} else {
// 保存目标路由,登录后重定向
authStore.setRedirectPath(to.fullPath)
next('/login')
}
} else {
next()
}
})
// 登录后重定向
router.afterEach((to) => {
const authStore = useAuthStore()
if (to.path === '/login' && authStore.redirectPath) {
const redirect = authStore.redirectPath
authStore.clearRedirectPath()
router.push(redirect)
}
})
}
// main.ts 中使用
import { setupAuthGuards } from './router/guards'
setupAuthGuards(router)
配置路由元信息:
const routes = [
{
path: '/public',
component: PublicPage,
meta: { requiresAuth: false }
},
{
path: '/dashboard',
component: Dashboard,
meta: {
requiresAuth: true,
roles: ['user', 'admin'],
title: 'Dashboard'
}
},
{
path: '/admin',
component: AdminPanel,
meta: {
requiresAuth: true,
roles: ['admin'],
title: 'Admin Panel'
}
}
]
🔄 状态保持与恢复
9. 滚动位置管理
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
// 返回前进/后退时保存的位置
return savedPosition
} else if (to.hash) {
// 跳转到锚点
return { el: to.hash, behavior: 'smooth' }
} else {
// 其他情况滚到顶部
return { top: 0, behavior: 'smooth' }
}
}
})
10. 动态修改路由
// 运行时添加新路由
router.addRoute({
path: '/new-page',
component: NewPage
})
// 添加到特定父路由
router.addRoute('DashboardParent', {
path: 'new-tab',
component: NewTab
})
// 删除路由
const removeRoute = router.addRoute({
path: '/temp',
component: TempPage
})
removeRoute() // 调用返回值以删除
📈 性能优化最佳实践
性能对比表
| 优化方案 | 首屏加载 | 路由切换 | 包大小 | 推荐度 |
|---|---|---|---|---|
| 普通导入 | ❌ | ✅ | ❌ | ⭐ |
| 路由懒加载 | ⭐⭐ | ⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| 预加载路由 | ⭐⭐⭐ | ✅ | ⭐ | ⭐⭐ |
| 分组懒加载 | ⭐⭐ | ⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
关键优化技巧
// 1. 合理分组懒加载
const routes = [
{
path: '/dashboard',
component: () => import(/* webpackChunkName: "admin" */ '../layouts/AdminLayout.vue'),
children: [
{ path: 'home', component: () => import(/* webpackChunkName: "admin" */ '../pages/DashboardHome.vue') },
{ path: 'users', component: () => import(/* webpackChunkName: "admin" */ '../pages/Users.vue') }
]
}
]
// 2. 预加载关键路由
if (isMobile()) {
// 移动端不预加载,节省带宽
} else {
// 桌面端预加载用户常访问的页面
const link = document.createElement('link')
link.rel = 'prefetch'
link.href = '/js/page.chunk.js'
document.head.appendChild(link)
}
// 3. 使用 preload vs prefetch
// preload:立即加载,用于当前页面立即需要的资源
// prefetch:空闲时加载,用于未来可能需要的资源
🐛 常见问题解决
问题 1:路由参数变化但组件不更新
<script setup lang="ts">
import { useRoute } from 'vue-router'
import { watch } from 'vue'
const route = useRoute()
const data = ref(null)
// ❌ 错误:只在组件挂载时执行一次
const loadData = async () => {
data.value = await fetchUserData(route.params.id)
}
// ✅ 正确:监听路由变化
watch(() => route.params.id, async (newId) => {
data.value = await fetchUserData(newId)
}, { immediate: true })
</script>
问题 2:导航守卫中的死循环
// ❌ 错误:可能导致无限循环
router.beforeEach((to, from, next) => {
if (needsRedirect(to)) {
next('/other-page') // 这会再次触发 beforeEach
}
})
// ✅ 正确:检查来源以避免循环
router.beforeEach((to, from, next) => {
if (needsRedirect(to) && !isAlreadyRedirected(from)) {
next('/other-page')
} else {
next()
}
})
问题 3:组件 keep-alive 与路由结合
<!-- App.vue -->
<template>
<keep-alive :include="cachedComponents">
<router-view />
</keep-alive>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
// 缓存特定路由的组件
const cachedComponents = computed(() => {
return ['Dashboard', 'List'] // 组件名称
})
</script>
🎓 最佳实践总结
路由设计原则
- 语义化 URL:
/users/123比/page?id=123更清晰 - 一致的命名:保持路由路径的命名规范
- 按需加载:大型应用使用路由懒加载
- 层级明确:使用嵌套路由表示页面层级关系
- 保护敏感路由:使用导航守卫实现权限控制
性能建议
// ✅ DO: 使用路由懒加载
const UserProfile = () => import('../pages/UserProfile.vue')
// ✅ DO: 分组相关路由
component: () => import(/* webpackChunkName: "admin" */ '../layouts/Admin.vue')
// ✅ DO: 利用元信息管理配置
meta: { requiresAuth: true, title: 'Page Title' }
// ❌ DON'T: 导入所有路由组件
import * as Pages from '../pages'
// ❌ DON'T: 在导航守卫中做重操作
// 应该在组件中异步加载数据
📚 扩展阅读
总结
Vue Router 4 提供了强大而灵活的路由系统:
- 基础路由:简单直观的声明式配置
- 高级功能:导航守卫、懒加载、嵌套路由
- 性能优化:通过合理的代码分割提升用户体验
- 类型安全:完整的 TypeScript 支持
掌握这些概念和技巧,你就能构建高效、可维护的单页应用。