Skip to main content

Vue Router路由管理

Vue Router是Vue.js的官方路由管理器,用于构建单页应用(SPA)。它与Vue.js深度集成,提供了声明式的路由配置、动态路由匹配、嵌套路由、导航守卫等强大功能。

核心价值

Vue Router优势

  • 🧭 声明式路由:通过配置定义应用路由结构
  • 🔄 动态路由:支持参数化路由和通配符
  • 🧩 组件化路由:每个路由对应一个组件
  • 🛡️ 路由守卫:控制路由的访问权限
  • 📱 响应式路由:与Vue响应式系统完美结合

1. Vue Router基础配置

1.1 安装与基本设置

bash
1# 安装Vue Router
2npm install vue-router@4 # 适用于Vue 3

在Vue 3项目中创建路由配置:

src/router/index.js
js
1import { createRouter, createWebHistory } from 'vue-router'
2import HomeView from '../views/HomeView.vue'
3import AboutView from '../views/AboutView.vue'
4import NotFound from '../views/NotFound.vue'
5
6const routes = [
7 {
8 path: '/',
9 name: 'home',
10 component: HomeView,
11 meta: {
12 title: '首页',
13 requiresAuth: false
14 }
15 },
16 {
17 path: '/about',
18 name: 'about',
19 component: AboutView,
20 meta: {
21 title: '关于我们',
22 requiresAuth: false
23 }
24 },
25 {
26 path: '/user/:id',
27 name: 'user',
28 component: () => import('../views/UserView.vue'), // 懒加载路由
29 meta: {
30 title: '用户详情',
31 requiresAuth: true
32 }
33 },
34 {
35 path: '/:pathMatch(.*)*',
36 name: 'not-found',
37 component: NotFound,
38 meta: {
39 title: '页面未找到'
40 }
41 }
42]
43
44const router = createRouter({
45 history: createWebHistory(), // HTML5 History模式
46 routes,
47 scrollBehavior(to, from, savedPosition) {
48 // 页面切换时滚动行为
49 if (savedPosition) {
50 return savedPosition // 如果是前进/后退按钮,恢复之前的位置
51 } else {
52 return { top: 0 } // 否则滚动到顶部
53 }
54 }
55})
56
57export default router

在主应用中注册路由:

src/main.js
js
1import { createApp } from 'vue'
2import App from './App.vue'
3import router from './router'
4
5const app = createApp(App)
6app.use(router)
7app.mount('#app')

1.2 路由视图与链接

在应用中使用路由导航和视图:

App.vue
vue
1<template>
2 <header>
3 <nav>
4 <RouterLink to="/">首页</RouterLink>
5 <RouterLink to="/about">关于</RouterLink>
6 <RouterLink :to="{ name: 'user', params: { id: userId } }">用户资料</RouterLink>
7 </nav>
8 </header>
9
10 <!-- 路由视图,路由匹配的组件会渲染在这里 -->
11 <RouterView v-slot="{ Component }">
12 <Transition name="fade" mode="out-in">
13 <component :is="Component" />
14 </Transition>
15 </RouterView>
16</template>
17
18<script setup>
19import { RouterLink, RouterView } from 'vue-router'
20import { ref } from 'vue'
21
22const userId = ref('12345')
23</script>
24
25<style scoped>
26nav {
27 padding: 1rem 0;
28}
29
30nav a {
31 margin-right: 1rem;
32}
33
34.fade-enter-active,
35.fade-leave-active {
36 transition: opacity 0.3s;
37}
38
39.fade-enter-from,
40.fade-leave-to {
41 opacity: 0;
42}
43</style>

2. 高级路由功能

2.1 嵌套路由

嵌套路由允许在父路由组件内部定义子路由,实现更复杂的UI结构。

嵌套路由配置示例
js
1const routes = [
2 {
3 path: '/dashboard',
4 component: DashboardLayout,
5 children: [
6 {
7 path: '', // 默认子路由,访问/dashboard时显示
8 name: 'dashboard-overview',
9 component: DashboardOverview
10 },
11 {
12 path: 'profile',
13 name: 'dashboard-profile',
14 component: UserProfile
15 },
16 {
17 path: 'settings',
18 name: 'dashboard-settings',
19 component: UserSettings
20 }
21 ]
22 }
23]

使用嵌套路由的父组件需要包含<RouterView>来显示子路由组件:

DashboardLayout.vue
vue
1<template>
2 <div class="dashboard-layout">
3 <aside class="sidebar">
4 <nav>
5 <RouterLink :to="{ name: 'dashboard-overview' }">概览</RouterLink>
6 <RouterLink :to="{ name: 'dashboard-profile' }">个人资料</RouterLink>
7 <RouterLink :to="{ name: 'dashboard-settings' }">设置</RouterLink>
8 </nav>
9 </aside>
10
11 <main class="content">
12 <!-- 子路由组件将在这里渲染 -->
13 <RouterView />
14 </main>
15 </div>
16</template>

2.2 动态路由匹配

动态路由支持在URL中包含参数,并在组件中访问这些参数:

UserView.vue
vue
1<template>
2 <div class="user-view">
3 <h1>用户详情</h1>
4 <div v-if="user">
5 <img :src="user.avatar" alt="User Avatar" />
6 <h2>{{ user.name }}</h2>
7 <p>ID: {{ $route.params.id }}</p>
8 <p>邮箱: {{ user.email }}</p>
9 </div>
10 <p v-else>加载用户数据中...</p>
11 </div>
12</template>
13
14<script setup>
15import { ref, onMounted, watch } from 'vue'
16import { useRoute } from 'vue-router'
17
18const route = useRoute()
19const user = ref(null)
20
21// 根据路由参数加载用户数据
22const fetchUserData = async (userId) => {
23 try {
24 // 模拟API调用
25 const response = await fetch(`/api/users/${userId}`)
26 if (!response.ok) throw new Error('用户数据获取失败')
27 user.value = await response.json()
28 } catch (error) {
29 console.error(error)
30 }
31}
32
33// 组件挂载时获取数据
34onMounted(() => {
35 fetchUserData(route.params.id)
36})
37
38// 当路由参数变化时重新获取数据
39watch(() => route.params.id, (newId) => {
40 fetchUserData(newId)
41})
42</script>

2.3 路由懒加载

路由懒加载可以提高应用性能,只有当路由被访问时才加载对应的组件:

js
1const routes = [
2 {
3 path: '/analytics',
4 name: 'analytics',
5 // 使用import动态导入实现懒加载
6 component: () => import('../views/AnalyticsView.vue'),
7 // 可以自定义加载中和错误状态的组件
8 meta: {
9 title: '数据分析'
10 }
11 },
12 {
13 path: '/reports',
14 name: 'reports',
15 component: () => import(/* webpackChunkName: "reports" */ '../views/ReportsView.vue')
16 // webpackChunkName可以指定打包后的文件名
17 }
18]

2.4 命名视图

命名视图允许在同一个路由下显示多个组件,适用于复杂的布局:

js
1const routes = [
2 {
3 path: '/workspace',
4 components: {
5 default: WorkspaceMain,
6 sidebar: WorkspaceSidebar,
7 toolbar: WorkspaceToolbar
8 }
9 }
10]

在模板中使用命名视图:

vue
1<template>
2 <div class="layout">
3 <RouterView name="toolbar" class="toolbar" />
4 <RouterView name="sidebar" class="sidebar" />
5 <RouterView class="main-content" /> <!-- default视图 -->
6 </div>
7</template>

3. 导航守卫

3.1 全局导航守卫

全局导航守卫可以用于权限验证、页面标题设置等功能:

src/router/index.js
js
1// 全局前置守卫
2router.beforeEach((to, from, next) => {
3 // 设置文档标题
4 document.title = to.meta.title || '默认标题'
5
6 // 权限检查
7 if (to.meta.requiresAuth) {
8 const authService = useAuthService()
9 if (!authService.isLoggedIn()) {
10 // 未登录时重定向到登录页
11 next({
12 path: '/login',
13 query: { redirect: to.fullPath } // 保存原目标路径
14 })
15 return
16 }
17 }
18
19 // 继续导航
20 next()
21})
22
23// 全局解析守卫
24router.beforeResolve(async (to, from, next) => {
25 try {
26 // 在导航被确认前,加载与视图相关的数据
27 for (const component of to.matched) {
28 if (component.meta.fetchData) {
29 await component.meta.fetchData()
30 }
31 }
32 next()
33 } catch (error) {
34 console.error('数据加载失败', error)
35 next(false) // 中止导航
36 }
37})
38
39// 全局后置钩子
40router.afterEach((to, from) => {
41 // 导航完成后的逻辑
42 const analyticsService = useAnalyticsService()
43 analyticsService.trackPageView(to.fullPath)
44})

3.2 路由独享守卫

可以为特定路由配置守卫:

js
1const routes = [
2 {
3 path: '/admin',
4 component: AdminPanel,
5 beforeEnter: (to, from, next) => {
6 const authService = useAuthService()
7 if (!authService.hasAdminRights()) {
8 next('/forbidden')
9 } else {
10 next()
11 }
12 }
13 }
14]

3.3 组件内守卫

可以在组件内部定义路由守卫:

vue
1<script setup>
2import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
3import { ref } from 'vue'
4
5const hasUnsavedChanges = ref(false)
6
7// 离开路由前确认
8onBeforeRouteLeave((to, from, next) => {
9 if (hasUnsavedChanges.value) {
10 if (confirm('有未保存的更改,确定要离开吗?')) {
11 next()
12 } else {
13 next(false)
14 }
15 } else {
16 next()
17 }
18})
19
20// 路由参数更新时的处理
21onBeforeRouteUpdate(async (to, from, next) => {
22 if (to.params.id !== from.params.id) {
23 try {
24 // 加载新的数据
25 await loadData(to.params.id)
26 next()
27 } catch (error) {
28 console.error('数据加载失败', error)
29 next(false)
30 }
31 } else {
32 next()
33 }
34})
35</script>

4. 路由模式与历史记录

Vue Router支持多种路由模式:

js
1// HTML5 History模式
2const router = createRouter({
3 history: createWebHistory(),
4 routes
5})
6
7// Hash模式
8const router = createRouter({
9 history: createWebHashHistory(),
10 routes
11})
12
13// 内存模式(用于SSR等场景)
14const router = createRouter({
15 history: createMemoryHistory(),
16 routes
17})

4.1 History模式配置

History模式需要服务器端配置支持,以防止刷新页面时出现404错误:

nginx
1server {
2 listen 80;
3 server_name example.com;
4 root /var/www/html;
5 index index.html;
6
7 location / {
8 try_files $uri $uri/ /index.html;
9 }
10}

5. 路由最佳实践

5.1 模块化路由配置

对于大型应用,可以将路由配置模块化:

src/router/modules/user.js
js
1export default [
2 {
3 path: '/users',
4 component: () => import('@/views/users/UserList.vue'),
5 meta: { title: '用户列表' }
6 },
7 {
8 path: '/users/:id',
9 component: () => import('@/views/users/UserDetail.vue'),
10 meta: { title: '用户详情' }
11 },
12 // 其他用户相关路由...
13]
src/router/index.js
js
1import { createRouter, createWebHistory } from 'vue-router'
2import userRoutes from './modules/user'
3import productRoutes from './modules/product'
4import orderRoutes from './modules/order'
5
6const routes = [
7 {
8 path: '/',
9 component: () => import('@/views/Home.vue'),
10 meta: { title: '首页' }
11 },
12 // 导入各模块路由
13 ...userRoutes,
14 ...productRoutes,
15 ...orderRoutes,
16 {
17 path: '/:pathMatch(.*)*',
18 component: () => import('@/views/NotFound.vue'),
19 meta: { title: '页面未找到' }
20 }
21]
22
23const router = createRouter({
24 history: createWebHistory(),
25 routes
26})
27
28export default router

5.2 路由元信息的高级用法

路由元信息可以存储与路由相关的自定义数据:

js
1const routes = [
2 {
3 path: '/dashboard',
4 component: Dashboard,
5 meta: {
6 title: '仪表盘',
7 icon: 'dashboard',
8 requiresAuth: true,
9 permissions: ['view-dashboard'],
10 layout: 'admin',
11 transition: 'fade',
12 breadcrumb: [{ name: '首页', path: '/' }, { name: '仪表盘' }],
13 cacheable: true
14 }
15 }
16]

使用路由元信息构建导航菜单:

SideMenu.vue
vue
1<template>
2 <nav class="side-menu">
3 <ul>
4 <li v-for="route in navigationRoutes" :key="route.path">
5 <RouterLink :to="route.path">
6 <i :class="route.meta.icon"></i>
7 {{ route.meta.title }}
8 </RouterLink>
9 </li>
10 </ul>
11 </nav>
12</template>
13
14<script setup>
15import { computed } from 'vue'
16import { useRouter } from 'vue-router'
17
18const router = useRouter()
19
20// 过滤出需要在导航中显示的路由
21const navigationRoutes = computed(() => {
22 return router.options.routes.filter(route => {
23 return route.meta?.showInNav && hasPermission(route.meta.permissions)
24 })
25})
26
27const hasPermission = (requiredPermissions) => {
28 // 权限检查逻辑
29 const userPermissions = getCurrentUserPermissions()
30 if (!requiredPermissions) return true
31 return requiredPermissions.some(perm => userPermissions.includes(perm))
32}
33</script>

5.3 路由权限管理

实现基于角色的路由权限控制:

src/permission.js
js
1import router from './router'
2import store from './store'
3
4// 白名单路由(无需登录)
5const whiteList = ['/login', '/register', '/forgot-password']
6
7router.beforeEach(async (to, from, next) => {
8 // 获取用户令牌
9 const hasToken = localStorage.getItem('token')
10
11 if (hasToken) {
12 if (to.path === '/login') {
13 // 已登录则重定向到首页
14 next({ path: '/' })
15 } else {
16 // 判断用户是否已获取角色信息
17 const hasRoles = store.getters.roles && store.getters.roles.length > 0
18 if (hasRoles) {
19 next()
20 } else {
21 try {
22 // 获取用户信息(包括角色)
23 const { roles } = await store.dispatch('user/getUserInfo')
24
25 // 根据角色生成可访问路由
26 const accessibleRoutes = await store.dispatch('permission/generateRoutes', roles)
27
28 // 动态添加路由
29 accessibleRoutes.forEach(route => {
30 router.addRoute(route)
31 })
32
33 // 确保addRoute完成
34 next({ ...to, replace: true })
35 } catch (error) {
36 // 出错则重置令牌并重定向到登录页
37 await store.dispatch('user/resetToken')
38 next(`/login?redirect=${to.path}`)
39 }
40 }
41 }
42 } else {
43 // 未登录
44 if (whiteList.includes(to.path)) {
45 // 在白名单中,直接访问
46 next()
47 } else {
48 // 否则重定向到登录页
49 next(`/login?redirect=${to.path}`)
50 }
51 }
52})

6. 与状态管理结合

Vue Router与Pinia结合使用示例:

src/stores/routerStore.js
js
1import { defineStore } from 'pinia'
2import { useRouter, useRoute } from 'vue-router'
3
4export const useRouterStore = defineStore('router', () => {
5 const router = useRouter()
6 const route = useRoute()
7
8 // 导航历史
9 const navigationHistory = ref([])
10
11 // 添加历史记录
12 function addToHistory(route) {
13 // 避免重复
14 if (!navigationHistory.value.find(item => item.path === route.path)) {
15 navigationHistory.value.push({
16 path: route.path,
17 name: route.name,
18 title: route.meta.title,
19 timestamp: Date.now()
20 })
21
22 // 限制历史记录数量
23 if (navigationHistory.value.length > 10) {
24 navigationHistory.value.shift()
25 }
26 }
27 }
28
29 // 导航到上一个页面
30 function goBack() {
31 if (navigationHistory.value.length > 1) {
32 // 移除当前页面
33 navigationHistory.value.pop()
34 // 获取上一个页面
35 const prevPage = navigationHistory.value[navigationHistory.value.length - 1]
36 router.push(prevPage.path)
37 } else {
38 router.push('/')
39 }
40 }
41
42 // 快捷导航方法
43 function navigateTo(routeName, params = {}, query = {}) {
44 router.push({ name: routeName, params, query })
45 }
46
47 // 跟踪当前路由
48 const currentRouteMeta = computed(() => route.meta)
49 const currentPath = computed(() => route.path)
50 const isActiveRoute = (path) => route.path === path
51
52 return {
53 navigationHistory,
54 addToHistory,
55 goBack,
56 navigateTo,
57 currentRouteMeta,
58 currentPath,
59 isActiveRoute
60 }
61})

在组件中使用:

vue
1<template>
2 <div>
3 <button @click="routerStore.goBack()">返回上一页</button>
4 <button @click="routerStore.navigateTo('dashboard')">前往仪表盘</button>
5
6 <div v-if="routerStore.navigationHistory.length > 0">
7 <h3>浏览历史</h3>
8 <ul>
9 <li v-for="(item, index) in routerStore.navigationHistory" :key="index">
10 {{ item.title || item.name }} - {{ new Date(item.timestamp).toLocaleTimeString() }}
11 </li>
12 </ul>
13 </div>
14 </div>
15</template>
16
17<script setup>
18import { useRouterStore } from '../stores/routerStore'
19
20const routerStore = useRouterStore()
21</script>

7. Vue Router与SSR

Vue Router在服务器端渲染(SSR)环境中的使用:

server.js (基于Express和Vue SSR)
js
1const express = require('express')
2const { renderToString } = require('vue/server-renderer')
3const { createSSRApp } = require('vue')
4const { createRouter, createMemoryHistory } = require('vue-router')
5const fs = require('fs')
6const path = require('path')
7
8const server = express()
9
10server.get('*', async (req, res) => {
11 try {
12 // 1. 为每个请求创建新的Vue应用实例
13 const app = createSSRApp(/* ... */)
14
15 // 2. 使用内存历史模式创建router
16 const router = createRouter({
17 history: createMemoryHistory(),
18 routes: [/* 路由配置 */]
19 })
20
21 app.use(router)
22
23 // 3. 将请求的URL设置为路由位置
24 await router.push(req.url)
25 await router.isReady()
26
27 // 4. 渲染应用为HTML
28 const appContent = await renderToString(app)
29
30 // 5. 注入渲染的HTML到模板中
31 const html = fs.readFileSync(
32 path.join(__dirname, 'index.html'),
33 'utf-8'
34 ).replace('<!--app-html-->', appContent)
35
36 // 6. 返回页面
37 res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
38 } catch (error) {
39 console.error(error)
40 res.status(500).end('Internal Server Error')
41 }
42})
43
44server.listen(3000)

参与讨论