1. 为什么需要专门复习Vue路由?
路由系统是现代前端框架的核心能力之一。在Vue 3项目中,vue-router作为官方路由解决方案,承担着单页应用(SPA)的导航管理重任。根据我的项目经验,80%的页面跳转异常都源于路由配置不当。这份手册将带你重新梳理:
- 从基础的路由表配置到高级的导航守卫
- 从静态路由匹配到动态路由权限控制
- 从编程式导航到路由组件懒加载
通过典型场景的代码演示,帮你构建完整的路由知识体系。以下是本手册会重点覆盖的技术要点:
- 路由基础配置与核心API
- 动态路由匹配的三种模式
- 嵌套路由的组件组织技巧
- 导航守卫的实战应用
- 路由元信息与权限控制
- 滚动行为与过渡动效
- 常见问题排查指南
2. 路由基础配置与核心API
2.1 初始化路由实例
在Vue 3中创建路由实例的推荐方式:
javascript复制import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(), // 使用HTML5历史模式
routes: [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue')
}
]
})
关键配置项说明:
history: 推荐生产环境使用createWebHistory(需服务器配合)routes: 路由配置数组,每个路由对象包含:path: URL路径(支持动态段)name: 命名路由(可选但建议)component: 路由组件(推荐懒加载)
注意:开发环境若遇到页面刷新404,需配置开发服务器支持History模式
2.2 路由视图与导航链接
基础模板结构:
html复制<!-- App.vue -->
<template>
<nav>
<router-link to="/">首页</router-link>
<router-link :to="{ name: 'About' }">关于</router-link>
</nav>
<router-view />
</template>
router-link的进阶用法:
active-class: 自定义激活状态类名exact-active-class: 精确匹配时的类名v-slotAPI:完全自定义渲染
html复制<router-link
to="/about"
custom
v-slot="{ href, isActive }"
>
<a :href="href" :class="{ active: isActive }">关于我们</a>
</router-link>
3. 动态路由匹配实战
3.1 参数化路由配置
动态段通过冒号:标记:
javascript复制{
path: '/user/:id',
component: User,
props: true // 推荐开启props传参
}
在组件中获取参数:
javascript复制// 选项式API
export default {
props: ['id'],
created() {
console.log(this.id)
}
}
// 组合式API
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id)
3.2 高级匹配模式
-
多段动态参数:
javascript复制path: '/user/:id/post/:postId' -
可选参数:
javascript复制path: '/user/:id?' -
正则约束:
javascript复制path: '/user/:id(\\d+)' // 只匹配数字ID
3.3 路由优先级规则
当多个路由可能匹配时:
- 先定义的路由优先级更高
- 静态路径优先于动态路径
- 动态段多的路径优先级更低
典型问题场景:
javascript复制routes: [
{ path: '/user/:id', component: User },
{ path: '/user/create', component: CreateUser } // 永远不会匹配
]
解决方案:调整顺序或使用path-to-regexp的严格模式
4. 嵌套路由与命名视图
4.1 嵌套路由配置
在父路由中使用children属性:
javascript复制{
path: '/dashboard',
component: DashboardLayout,
children: [
{
path: '', // 默认子路由
component: DashboardHome
},
{
path: 'settings',
component: DashboardSettings
}
]
}
注意:子路由的path不要以
/开头
4.2 命名视图布局
多视图出口场景:
html复制<router-view name="header" />
<router-view />
<router-view name="footer" />
对应路由配置:
javascript复制{
path: '/admin',
components: {
default: AdminMain,
header: AdminHeader,
footer: AdminFooter
}
}
5. 导航守卫深度解析
5.1 守卫类型与执行顺序
完整的导航解析流程:
- 导航被触发
- 调用失活组件的
beforeRouteLeave - 调用全局
beforeEach - 调用重用组件的
beforeRouteUpdate - 调用路由配置的
beforeEnter - 解析异步路由组件
- 调用激活组件的
beforeRouteEnter - 调用全局
beforeResolve - 导航确认
- 调用全局
afterEach - 触发DOM更新
5.2 典型应用场景
权限控制示例:
javascript复制router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !store.state.user) {
next({ name: 'Login' })
} else {
next()
}
})
页面访问统计:
javascript复制router.afterEach((to) => {
analytics.trackPageView(to.fullPath)
})
动态修改页面标题:
javascript复制router.afterEach((to) => {
document.title = to.meta.title || '默认标题'
})
6. 路由元信息与高级功能
6.1 路由元信息配置
javascript复制{
path: '/admin',
meta: {
requiresAuth: true,
roles: ['admin']
}
}
在导航守卫中访问:
javascript复制router.beforeEach((to, from, next) => {
const requiredRoles = to.meta.roles
// 权限校验逻辑...
})
6.2 滚动行为控制
javascript复制const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else if (to.hash) {
return {
el: to.hash,
behavior: 'smooth'
}
} else {
return { top: 0 }
}
}
})
6.3 路由过渡动效
html复制<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
配套CSS:
css复制.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
7. 性能优化与最佳实践
7.1 路由懒加载
推荐使用动态import:
javascript复制{
path: '/settings',
component: () => import('@/views/Settings.vue')
}
Webpack魔法注释(可选):
javascript复制component: () => import(/* webpackChunkName: "settings" */ '@/views/Settings.vue')
7.2 路由组件预加载
在父组件中:
javascript复制import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
onMounted(() => {
// 预加载可能需要的路由组件
router.getMatchedComponents('/dashboard')
})
7.3 路由分割策略
按功能模块分组:
javascript复制// admin.routes.js
export default [
{ path: 'users', component: () => import('./Users.vue') },
{ path: 'roles', component: () => import('./Roles.vue') }
]
// main.js
import adminRoutes from './admin.routes'
const router = createRouter({
routes: [
{
path: '/admin',
component: () => import('./AdminLayout.vue'),
children: adminRoutes
}
]
})
8. 常见问题排查指南
8.1 路由跳转不生效
检查清单:
- 路由表是否正确定义
router-link的to属性是否正确- 是否在导航守卫中调用了
next() - 目标路由是否需要权限验证
8.2 动态路由组件不更新
解决方案:
- 使用
beforeRouteUpdate守卫 - 在组件中添加
key属性:
html复制<router-view :key="$route.fullPath" />
8.3 路由重复跳报错
在路由跳转时捕获异常:
javascript复制router.push('/dashboard').catch(err => {
if (err.name !== 'NavigationDuplicated') {
console.error(err)
}
})
或者扩展Router原型:
javascript复制const originalPush = router.push
router.push = function push(location) {
return originalPush.call(this, location).catch(err => {
if (err.name !== 'NavigationDuplicated') {
throw err
}
})
}
9. 项目实战:权限路由系统
9.1 路由表结构设计
javascript复制// 基础路由(无需权限)
const constantRoutes = [
{
path: '/login',
component: () => import('@/views/Login.vue')
}
]
// 异步路由(需权限)
const asyncRoutes = [
{
path: '/admin',
meta: { roles: ['admin'] },
children: [
/* admin子路由 */
]
}
]
9.2 动态路由添加
用户登录后:
javascript复制// 过滤有权限的路由
const accessedRoutes = filterRoutes(asyncRoutes, user.roles)
// 添加路由
accessedRoutes.forEach(route => {
router.addRoute(route)
})
// 添加404路由(确保最后添加)
router.addRoute({ path: '/:pathMatch(.*)*', component: NotFound })
9.3 路由重置方案
javascript复制function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // 重置路由匹配器
}
10. Vue Router 4.x 新特性
10.1 组合式API支持
javascript复制import { useRouter, useRoute } from 'vue-router'
export default {
setup() {
const router = useRouter()
const route = useRoute()
const navigate = () => {
router.push({ name: 'Home' })
}
return { navigate }
}
}
10.2 路由状态共享
javascript复制// 共享路由状态
import { reactive } from 'vue'
import { useRoute } from 'vue-router'
export function useRouteState() {
const route = useRoute()
const state = reactive({
params: route.params,
query: route.query
})
return { state }
}
10.3 路由懒加载改进
使用defineAsyncComponent:
javascript复制import { defineAsyncComponent } from 'vue'
{
path: '/profile',
component: defineAsyncComponent(() =>
import('@/views/Profile.vue')
)
}
11. 测试与调试技巧
11.1 路由单元测试
使用@vue/test-utils:
javascript复制import { mount } from '@vue/test-utils'
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [{ path: '/', component: { template: '<div>Home</div>' } }]
})
test('navigates to home', async () => {
const wrapper = mount(App, {
global: {
plugins: [router]
}
})
await router.push('/')
expect(wrapper.text()).toContain('Home')
})
11.2 路由调试工具
- 使用Vue DevTools的路由面板
- 打印路由对象:
javascript复制router.afterEach((to) => {
console.log('Navigation to:', to.fullPath)
})
- 路由变更监听:
javascript复制watch(
() => route.fullPath,
(newPath) => {
console.log('Route changed:', newPath)
}
)
12. 从Vue 2迁移指南
12.1 破坏性变更清单
new Router()→createRouter()mode: 'history'→history: createWebHistory()- 移除
*通配路由,改用/:pathMatch(.*)* router.app→ 使用app.use(router)scrollBehavior返回值格式变更
12.2 迁移步骤示例
Vue 2配置:
javascript复制const router = new VueRouter({
mode: 'history',
routes: [
{ path: '*', component: NotFound }
]
})
Vue 3等效配置:
javascript复制import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/:pathMatch(.*)*', component: NotFound }
]
})
13. 与其他状态库集成
13.1 与Pinia配合使用
在导航守卫中访问store:
javascript复制import { useAuthStore } from '@/stores/auth'
router.beforeEach((to) => {
const auth = useAuthStore()
if (to.meta.requiresAuth && !auth.isLoggedIn) {
return '/login'
}
})
13.2 与Vuex集成
传统方式:
javascript复制router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !store.state.user) {
next('/login')
} else {
next()
}
})
组合式风格:
javascript复制import { useStore } from 'vuex'
router.beforeEach((to) => {
const store = useStore()
// 访问store状态...
})
14. 服务端渲染(SSR)特别处理
14.1 路由创建差异
javascript复制// client.js
import { createSSRApp } from 'vue'
import { createRouter } from './router'
const app = createSSRApp(App)
const router = createRouter()
app.use(router)
router.isReady().then(() => {
app.mount('#app')
})
// server.js
import { createMemoryHistory } from 'vue-router'
export function createRouter() {
return createRouter({
history: createMemoryHistory(),
routes
})
}
14.2 数据预取策略
使用serverPrefetch:
javascript复制export default {
async serverPrefetch() {
await this.fetchData(this.$route.params.id)
},
methods: {
fetchData(id) {
// 获取数据...
}
}
}
15. 微前端路由方案
15.1 主应用配置
javascript复制const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/app1/*',
component: () => import('@/layouts/MicroApp.vue'),
meta: { microApp: 'app1' }
}
]
})
15.2 子应用适配
子应用需使用base路由:
javascript复制let router = null
function mount(props) {
router = createRouter({
history: createWebHistory(props.baseUrl || '/'),
routes
})
app.use(router)
router.isReady().then(() => {
app.mount(props.container || '#app')
})
}
16. 移动端路由特殊处理
16.1 转场动画优化
html复制<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'fade'">
<component :is="Component" />
</transition>
</router-view>
16.2 手势返回支持
与@vueuse/gesture集成:
javascript复制import { useSwipe } from '@vueuse/gesture'
useSwipe(containerEl, {
onSwipeEnd(e) {
if (e.direction[0] > 0) {
router.go(-1)
}
}
})
17. TypeScript深度集成
17.1 路由类型定义
扩展RouteMeta接口:
typescript复制import 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
requiresAuth?: boolean
roles?: string[]
transition?: string
}
}
17.2 类型安全导航
typescript复制router.push({
name: 'UserProfile', // 自动补全命名路由
params: { id: 123 }, // 校验参数类型
query: { tab: 'info' }
})
18. 性能监控与分析
18.1 路由切换耗时统计
javascript复制router.beforeEach((to, from, next) => {
const startTime = performance.now()
next()
router.afterEach(() => {
const duration = performance.now() - startTime
console.log(`路由切换耗时: ${duration.toFixed(2)}ms`)
})
})
18.2 组件加载追踪
javascript复制router.beforeResolve((to) => {
const components = to.matched.flatMap(record =>
Object.values(record.components)
)
return Promise.all(components.map(component => {
if (typeof component === 'function') {
return component()
}
}))
})
19. 安全最佳实践
19.1 路由参数验证
javascript复制{
path: '/user/:id',
component: User,
beforeEnter: (to) => {
if (!/^\d+$/.test(to.params.id)) {
return '/not-found'
}
}
}
19.2 XSS防护
避免直接使用路由参数:
html复制<!-- 危险 -->
<div v-html="route.query.content"></div>
<!-- 安全 -->
<div>{{ route.query.content }}</div>
20. 项目结构建议
推荐的路由模块化组织:
code复制src/
router/
index.js # 主路由入口
routes/
auth.js # 认证相关路由
admin.js # 管理后台路由
public.js # 公开路由
guards/
auth.js # 认证守卫
permission.js # 权限守卫
utils/
routeUtils.js # 路由工具函数
在大型项目中,这种结构可以保持路由配置的可维护性。每个功能模块可以导出自己的路由配置,然后在主入口文件中合并:
javascript复制// router/index.js
import { createRouter } from 'vue-router'
import authRoutes from './routes/auth'
import adminRoutes from './routes/admin'
const router = createRouter({
history: createWebHistory(),
routes: [
...authRoutes,
...adminRoutes,
{ path: '/:pathMatch(.*)*', component: NotFound }
]
})
// 添加全局守卫
import authGuard from './guards/auth'
router.beforeEach(authGuard)
这种组织方式特别适合:
- 多人协作项目
- 需要按功能模块懒加载路由配置的场景
- 需要灵活启用/禁用某些路由集的场景