活动公告

系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

深入理解Vue3 Vue Router高级用法构建可扩展的前端路由架构实战

SunJu_FaceMall

3万

主题

2720

科技点

3万

积分

执行版主

碾压王

积分
32881

塔罗立华奏

执行版主 发表于 2025-8-27 19:00:27 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

在现代前端开发中,Vue.js已经成为了最受欢迎的JavaScript框架之一,而Vue3作为其最新版本,带来了更多性能优化和新特性。Vue Router作为Vue.js官方的路由管理器,与Vue3紧密集成,为我们提供了构建单页应用(SPA)的强大能力。本文将深入探讨Vue3中Vue Router的高级用法,并通过实战案例展示如何构建一个可扩展、高性能的前端路由架构。

Vue3和Vue Router基础回顾

在深入高级用法之前,我们先简单回顾一下Vue3和Vue Router的基础知识。

Vue3核心特性

Vue3引入了许多新特性,其中最重要的是Composition API,它提供了一种更灵活的组织组件逻辑的方式:
  1. import { ref, computed, onMounted } from 'vue'
  2. export default {
  3.   setup() {
  4.     const count = ref(0)
  5.     const doubleCount = computed(() => count.value * 2)
  6.    
  7.     function increment() {
  8.       count.value++
  9.     }
  10.    
  11.     onMounted(() => {
  12.       console.log('Component mounted')
  13.     })
  14.    
  15.     return {
  16.       count,
  17.       doubleCount,
  18.       increment
  19.     }
  20.   }
  21. }
复制代码

Vue Router基础

Vue Router的基础用法包括定义路由、创建路由实例和挂载路由:
  1. // 1. 定义路由组件
  2. const Home = { template: '<div>Home</div>' }
  3. const About = { template: '<div>About</div>' }
  4. // 2. 定义路由
  5. const routes = [
  6.   { path: '/', component: Home },
  7.   { path: '/about', component: About }
  8. ]
  9. // 3. 创建路由实例
  10. const router = VueRouter.createRouter({
  11.   history: VueRouter.createWebHistory(),
  12.   routes
  13. })
  14. // 4. 创建并挂载根实例
  15. const app = Vue.createApp({})
  16. app.use(router)
  17. app.mount('#app')
复制代码

Vue Router高级特性详解

动态路由

动态路由允许我们根据不同的参数展示不同的内容,是构建可扩展路由架构的基础。
  1. const routes = [
  2.   // 动态字段以冒号开始
  3.   { path: '/user/:id', component: User }
  4. ]
复制代码

在组件中,我们可以通过useRoute获取路由参数:
  1. import { useRoute } from 'vue-router'
  2. export default {
  3.   setup() {
  4.     const route = useRoute()
  5.     console.log(route.params.id) // 访问动态参数
  6.   }
  7. }
复制代码

我们可以使用正则表达式来限制动态参数的匹配规则:
  1. const routes = [
  2.   // 只匹配数字id
  3.   { path: '/user/:id(\\d+)', component: User },
  4.   // 可选参数
  5.   { path: '/user/:id?', component: User },
  6.   // 重复参数
  7.   { path: '/user/:id(\\d+)+', component: User },
  8.   // 自定义正则
  9.   { path: '/user/:id(\\d{2,})', component: User }
  10. ]
复制代码

在大型应用中,我们可能需要根据用户权限或其他条件动态添加路由:
  1. // 添加路由
  2. router.addRoute({
  3.   path: '/admin',
  4.   component: Admin
  5. })
  6. // 添加嵌套路由
  7. router.addRoute('admin', {
  8.   path: 'dashboard',
  9.   component: AdminDashboard
  10. })
  11. // 检查路由是否存在
  12. console.log(router.hasRoute('admin')) // true
  13. // 删除路由
  14. router.removeRoute('admin')
复制代码

嵌套路由

嵌套路由允许我们在一个路由下嵌套子路由,构建复杂的页面结构。
  1. const routes = [
  2.   {
  3.     path: '/user',
  4.     component: User,
  5.     children: [
  6.       {
  7.         // 当 /user/profile 匹配成功
  8.         // UserProfile 将被渲染到 User 的 <router-view> 内部
  9.         path: 'profile',
  10.         component: UserProfile
  11.       },
  12.       {
  13.         // 当 /user/posts 匹配成功
  14.         // UserPosts 将被渲染到 User 的 <router-view> 内部
  15.         path: 'posts',
  16.         component: UserPosts
  17.       }
  18.     ]
  19.   }
  20. ]
复制代码

我们可以在一个路由中定义多个命名视图:
  1. const routes = [
  2.   {
  3.     path: '/settings',
  4.     components: {
  5.       default: Settings,
  6.       a: Sidebar,
  7.       b: Content
  8.     }
  9.   }
  10. ]
复制代码

在模板中:
  1. <router-view></router-view>
  2. <router-view name="a"></router-view>
  3. <router-view name="b"></router-view>
复制代码

命名路由

命名路由可以简化导航和编程式导航。
  1. const routes = [
  2.   {
  3.     path: '/user/:userId',
  4.     name: 'user',
  5.     component: User
  6.   }
  7. ]
复制代码
  1. <!-- 使用命名路由进行导航 -->
  2. <router-link :to="{ name: 'user', params: { userId: 123 } }">User</router-link>
复制代码
  1. // 编程式导航
  2. router.push({ name: 'user', params: { userId: 123 } })
复制代码

重定向和别名

重定向和别名提供了灵活的URL映射方式。
  1. const routes = [
  2.   { path: '/home', redirect: '/' },
  3.   // 动态重定向
  4.   { path: '/redirect-with-params/:id', redirect: to => {
  5.     // 方法接收目标路由作为参数
  6.     // return 重定向的字符串路径/路径对象
  7.     return { path: '/user', query: { id: to.params.id } }
  8.   }}
  9. ]
复制代码
  1. const routes = [
  2.   {
  3.     path: '/user',
  4.     component: User,
  5.     alias: '/people' // /user 的别名是 /people
  6.   }
  7. ]
复制代码

路由元信息

路由元信息允许我们附加自定义数据到路由记录上,这对于权限控制等功能非常有用。
  1. const routes = [
  2.   {
  3.     path: '/admin',
  4.     component: Admin,
  5.     meta: { requiresAuth: true, role: 'admin' }
  6.   },
  7.   {
  8.     path: '/profile',
  9.     component: Profile,
  10.     meta: { requiresAuth: true }
  11.   }
  12. ]
复制代码
  1. // 在导航守卫中访问
  2. router.beforeEach((to, from, next) => {
  3.   if (to.meta.requiresAuth && !isAuthenticated) {
  4.     next('/login')
  5.   } else {
  6.     next()
  7.   }
  8. })
复制代码

导航守卫

导航守卫允许我们在路由跳转前后执行代码,是实现权限控制、数据预取等功能的关键。
  1. router.beforeEach((to, from, next) => {
  2.   // to: 即将进入的目标路由
  3.   // from: 当前导航正要离开的路由
  4.   // next: 必须调用该方法来 resolve 这个钩子
  5.   
  6.   if (to.meta.requiresAuth && !isAuthenticated) {
  7.     next('/login')
  8.   } else {
  9.     next()
  10.   }
  11. })
复制代码
  1. router.beforeResolve(async to => {
  2.   if (to.meta.requiresData) {
  3.     await fetchData(to.params.id)
  4.   }
  5. })
复制代码
  1. router.afterEach((to, from) => {
  2.   // 适合用于修改页面标题等操作
  3.   document.title = to.meta.title || 'Default Title'
  4. })
复制代码
  1. const routes = [
  2.   {
  3.     path: '/admin',
  4.     component: Admin,
  5.     beforeEnter: (to, from, next) => {
  6.       if (!isAdmin()) {
  7.         next('/login')
  8.       } else {
  9.         next()
  10.       }
  11.     }
  12.   }
  13. ]
复制代码
  1. export default {
  2.   beforeRouteEnter(to, from, next) {
  3.     // 在渲染该组件的对应路由被验证前调用
  4.     // 不能获取组件实例 `this`!
  5.     next(vm => {
  6.       // 通过 `vm` 访问组件实例
  7.     })
  8.   },
  9.   beforeRouteUpdate(to, from, next) {
  10.     // 在当前路由改变,但是该组件被复用时调用
  11.     this.name = to.params.name
  12.     next()
  13.   },
  14.   beforeRouteLeave(to, from, next) {
  15.     // 在导航离开该组件的对应路由时调用
  16.     if (isDataSaved()) {
  17.       next()
  18.     } else {
  19.       next(false) // 取消导航
  20.     }
  21.   }
  22. }
复制代码

构建可扩展的路由架构

模块化路由设计

在大型应用中,将路由按功能模块划分是提高可维护性的关键。
  1. // router/modules/user.js
  2. export default {
  3.   path: '/user',
  4.   name: 'User',
  5.   component: () => import('@/views/user/index.vue'),
  6.   meta: { title: '用户管理', icon: 'user' },
  7.   children: [
  8.     {
  9.       path: 'list',
  10.       name: 'UserList',
  11.       component: () => import('@/views/user/list.vue'),
  12.       meta: { title: '用户列表' }
  13.     },
  14.     {
  15.       path: 'detail/:id',
  16.       name: 'UserDetail',
  17.       component: () => import('@/views/user/detail.vue'),
  18.       meta: { title: '用户详情' },
  19.       props: true
  20.     }
  21.   ]
  22. }
  23. // router/modules/product.js
  24. export default {
  25.   path: '/product',
  26.   name: 'Product',
  27.   component: () => import('@/views/product/index.vue'),
  28.   meta: { title: '产品管理', icon: 'product' },
  29.   children: [
  30.     {
  31.       path: 'list',
  32.       name: 'ProductList',
  33.       component: () => import('@/views/product/list.vue'),
  34.       meta: { title: '产品列表' }
  35.     },
  36.     {
  37.       path: 'detail/:id',
  38.       name: 'ProductDetail',
  39.       component: () => import('@/views/product/detail.vue'),
  40.       meta: { title: '产品详情' },
  41.       props: true
  42.     }
  43.   ]
  44. }
复制代码
  1. // router/index.js
  2. import { createRouter, createWebHistory } from 'vue-router'
  3. import userRoutes from './modules/user'
  4. import productRoutes from './modules/product'
  5. const routes = [
  6.   {
  7.     path: '/',
  8.     redirect: '/dashboard'
  9.   },
  10.   {
  11.     path: '/dashboard',
  12.     name: 'Dashboard',
  13.     component: () => import('@/views/dashboard/index.vue'),
  14.     meta: { title: '首页', icon: 'dashboard' }
  15.   },
  16.   {
  17.     path: '/login',
  18.     name: 'Login',
  19.     component: () => import('@/views/login/index.vue'),
  20.     meta: { title: '登录' }
  21.   },
  22.   {
  23.     path: '/404',
  24.     name: '404',
  25.     component: () => import('@/views/error/404.vue'),
  26.     meta: { title: '404' }
  27.   },
  28.   userRoutes,
  29.   productRoutes,
  30.   {
  31.     path: '/:pathMatch(.*)*',
  32.     redirect: '/404'
  33.   }
  34. ]
  35. const router = createRouter({
  36.   history: createWebHistory(),
  37.   routes
  38. })
  39. export default router
复制代码

路由权限控制

权限控制是企业级应用中必不可少的功能,我们可以通过路由元信息和导航守卫实现。
  1. // 权限配置
  2. const permission = {
  3.   admin: ['user', 'product', 'dashboard'],
  4.   editor: ['product', 'dashboard'],
  5.   user: ['dashboard']
  6. }
  7. // 获取用户角色
  8. function getUserRole() {
  9.   // 从localStorage或Vuex/Pinia中获取用户角色
  10.   return localStorage.getItem('userRole') || 'user'
  11. }
  12. // 检查权限
  13. function hasPermission(route) {
  14.   const role = getUserRole()
  15.   const requiredPermission = route.meta && route.meta.permission
  16.   
  17.   if (!requiredPermission) {
  18.     return true // 如果路由不需要权限,则允许访问
  19.   }
  20.   
  21.   return permission[role].includes(requiredPermission)
  22. }
  23. // 全局前置守卫
  24. router.beforeEach((to, from, next) => {
  25.   // 检查是否需要登录
  26.   if (to.meta.requiresAuth && !isAuthenticated()) {
  27.     next({ name: 'Login', query: { redirect: to.fullPath } })
  28.     return
  29.   }
  30.   
  31.   // 检查权限
  32.   if (!hasPermission(to)) {
  33.     next({ name: '403' }) // 假设有403页面
  34.     return
  35.   }
  36.   
  37.   next()
  38. })
复制代码

在更复杂的场景中,我们可能需要根据用户权限动态生成路由:
  1. // 动态添加路由
  2. function addPermissionRoutes() {
  3.   const role = getUserRole()
  4.   
  5.   // 根据角色获取路由配置
  6.   const permissionRoutes = getRoutesByRole(role)
  7.   
  8.   // 动态添加路由
  9.   permissionRoutes.forEach(route => {
  10.     router.addRoute(route)
  11.   })
  12. }
  13. // 在应用启动时调用
  14. addPermissionRoutes()
复制代码

路由懒加载

路由懒加载是优化应用性能的重要手段,可以按需加载页面组件。
  1. const routes = [
  2.   {
  3.     path: '/about',
  4.     name: 'About',
  5.     component: () => import('../views/About.vue')
  6.   }
  7. ]
复制代码

将同一模块的路由打包到同一个chunk中:
  1. const routes = [
  2.   {
  3.     path: '/user',
  4.     name: 'User',
  5.     component: () => import(/* webpackChunkName: "user" */ '../views/User.vue')
  6.   },
  7.   {
  8.     path: '/user/list',
  9.     name: 'UserList',
  10.     component: () => import(/* webpackChunkName: "user" */ '../views/UserList.vue')
  11.   }
  12. ]
复制代码

在用户可能访问某个路由前提前加载:
  1. // 在鼠标悬停时预加载
  2. <router-link to="/about" @mouseover="preloadAbout">About</router-link>
  3. function preloadAbout() {
  4.   import('../views/About.vue')
  5. }
复制代码

路由过渡动画

路由过渡动画可以提升用户体验,使页面切换更加流畅。
  1. <template>
  2.   <router-view v-slot="{ Component }">
  3.     <transition name="fade" mode="out-in">
  4.       <component :is="Component" />
  5.     </transition>
  6.   </router-view>
  7. </template>
  8. <style>
  9. .fade-enter-active,
  10. .fade-leave-active {
  11.   transition: opacity 0.3s ease;
  12. }
  13. .fade-enter-from,
  14. .fade-leave-to {
  15.   opacity: 0;
  16. }
  17. </style>
复制代码
  1. <template>
  2.   <router-view v-slot="{ Component, route }">
  3.     <transition :name="route.meta.transition || 'fade'" mode="out-in">
  4.       <component :is="Component" />
  5.     </transition>
  6.   </router-view>
  7. </template>
复制代码
  1. <template>
  2.   <router-view v-slot="{ Component, route }">
  3.     <transition :name="getTransitionName(route)" mode="out-in">
  4.       <keep-alive>
  5.         <component :is="Component" />
  6.       </keep-alive>
  7.     </transition>
  8.   </router-view>
  9. </template>
  10. <script>
  11. export default {
  12.   methods: {
  13.     getTransitionName(route) {
  14.       // 根据路由深度或其他条件返回不同的过渡名称
  15.       const depth = route.meta.depth || 0
  16.       return depth % 2 === 0 ? 'slide-left' : 'slide-right'
  17.     }
  18.   }
  19. }
  20. </script>
复制代码

路由数据预取

在进入路由前预取数据可以提升用户体验,避免页面加载后再显示加载状态。
  1. // 在路由配置中定义数据预取函数
  2. const routes = [
  3.   {
  4.     path: '/user/:id',
  5.     component: UserDetail,
  6.     meta: {
  7.       requiresData: true,
  8.       dataPreFetch: (route) => {
  9.         return fetchUserDetail(route.params.id)
  10.       }
  11.     }
  12.   }
  13. ]
  14. // 在全局解析守卫中预取数据
  15. router.beforeResolve(async (to) => {
  16.   if (to.meta.requiresData && to.meta.dataPreFetch) {
  17.     try {
  18.       // 预取数据并存储在全局状态中
  19.       const data = await to.meta.dataPreFetch(to)
  20.       store.commit('setPreFetchedData', { route: to.name, data })
  21.     } catch (error) {
  22.       // 处理错误
  23.       console.error('Data pre-fetch failed:', error)
  24.       return false // 取消导航
  25.     }
  26.   }
  27. })
复制代码
  1. export default {
  2.   async beforeRouteEnter(to, from, next) {
  3.     try {
  4.       const data = await fetchData(to.params.id)
  5.       next(vm => {
  6.         vm.setData(data)
  7.       })
  8.     } catch (error) {
  9.       next('/error') // 跳转到错误页面
  10.     }
  11.   }
  12. }
复制代码
  1. import { onBeforeRouteUpdate, useRoute } from 'vue-router'
  2. import { ref, watch } from 'vue'
  3. export default {
  4.   setup() {
  5.     const route = useRoute()
  6.     const data = ref(null)
  7.     const loading = ref(false)
  8.     const error = ref(null)
  9.    
  10.     async function fetchData(id) {
  11.       loading.value = true
  12.       error.value = null
  13.       
  14.       try {
  15.         data.value = await fetchUserData(id)
  16.       } catch (err) {
  17.         error.value = err
  18.       } finally {
  19.         loading.value = false
  20.       }
  21.     }
  22.    
  23.     // 初始加载
  24.     fetchData(route.params.id)
  25.    
  26.     // 路由更新时重新获取数据
  27.     onBeforeRouteUpdate(async (to) => {
  28.       if (to.params.id !== route.params.id) {
  29.         await fetchData(to.params.id)
  30.       }
  31.     })
  32.    
  33.     return {
  34.       data,
  35.       loading,
  36.       error
  37.     }
  38.   }
  39. }
复制代码

实战案例:构建一个大型单页应用的路由架构

让我们通过一个实战案例来综合运用上述技术,构建一个大型单页应用的路由架构。

项目结构设计
  1. src/
  2. ├── api/                # API请求
  3. ├── assets/             # 静态资源
  4. ├── components/         # 公共组件
  5. ├── composables/        # 组合式函数
  6. ├── layouts/            # 布局组件
  7. │   ├── AuthLayout.vue  # 认证布局
  8. │   └── MainLayout.vue  # 主布局
  9. ├── router/             # 路由配置
  10. │   ├── guards.js       # 路由守卫
  11. │   ├── modules/        # 路由模块
  12. │   │   ├── dashboard.js
  13. │   │   ├── user.js
  14. │   │   ├── product.js
  15. │   │   └── settings.js
  16. │   └── index.js        # 路主路由配置
  17. ├── stores/             # Pinia状态管理
  18. ├── utils/              # 工具函数
  19. └── views/              # 页面组件
  20.     ├── dashboard/
  21.     ├── user/
  22.     ├── product/
  23.     ├── settings/
  24.     └── error/
复制代码

路由模块划分
  1. // src/router/index.js
  2. import { createRouter, createWebHistory } from 'vue-router'
  3. import setupGuards from './guards'
  4. // 基础路由
  5. const routes = [
  6.   {
  7.     path: '/login',
  8.     name: 'Login',
  9.     component: () => import('@/views/login/index.vue'),
  10.     meta: {
  11.       title: '登录',
  12.       layout: 'AuthLayout'
  13.     }
  14.   },
  15.   {
  16.     path: '/404',
  17.     name: '404',
  18.     component: () => import('@/views/error/404.vue'),
  19.     meta: {
  20.       title: '页面不存在',
  21.       layout: 'MainLayout'
  22.     }
  23.   },
  24.   {
  25.     path: '/403',
  26.     name: '403',
  27.     component: () => import('@/views/error/403.vue'),
  28.     meta: {
  29.       title: '无权限访问',
  30.       layout: 'MainLayout'
  31.     }
  32.   },
  33.   {
  34.     path: '/:pathMatch(.*)*',
  35.     redirect: '/404'
  36.   }
  37. ]
  38. const router = createRouter({
  39.   history: createWebHistory(),
  40.   routes
  41. })
  42. // 设置路由守卫
  43. setupGuards(router)
  44. export default router
复制代码
  1. // src/router/modules/dashboard.js
  2. export default {
  3.   path: '/',
  4.   name: 'Dashboard',
  5.   component: () => import('@/layouts/MainLayout.vue'),
  6.   redirect: '/dashboard',
  7.   meta: {
  8.     title: '控制台',
  9.     icon: 'dashboard',
  10.     requiresAuth: true
  11.   },
  12.   children: [
  13.     {
  14.       path: 'dashboard',
  15.       name: 'DashboardHome',
  16.       component: () => import('@/views/dashboard/index.vue'),
  17.       meta: {
  18.         title: '首页',
  19.         icon: 'home',
  20.         affix: true  // 固定标签页
  21.       }
  22.     },
  23.     {
  24.       path: 'workbench',
  25.       name: 'Workbench',
  26.       component: () => import('@/views/dashboard/workbench.vue'),
  27.       meta: {
  28.         title: '工作台',
  29.         icon: 'workbench'
  30.       }
  31.     },
  32.     {
  33.       path: 'analysis',
  34.       name: 'Analysis',
  35.       component: () => import('@/views/dashboard/analysis.vue'),
  36.       meta: {
  37.         title: '数据分析',
  38.         icon: 'chart',
  39.         keepAlive: true  // 缓存页面
  40.       }
  41.     }
  42.   ]
  43. }
  44. // src/router/modules/user.js
  45. export default {
  46.   path: '/user',
  47.   name: 'User',
  48.   component: () => import('@/layouts/MainLayout.vue'),
  49.   redirect: '/user/list',
  50.   meta: {
  51.     title: '用户管理',
  52.     icon: 'user',
  53.     requiresAuth: true,
  54.     permission: 'user'  // 需要user权限
  55.   },
  56.   children: [
  57.     {
  58.       path: 'list',
  59.       name: 'UserList',
  60.       component: () => import('@/views/user/list.vue'),
  61.       meta: {
  62.         title: '用户列表',
  63.         icon: 'list'
  64.       }
  65.     },
  66.     {
  67.       path: 'detail/:id',
  68.       name: 'UserDetail',
  69.       component: () => import('@/views/user/detail.vue'),
  70.       props: true,
  71.       meta: {
  72.         title: '用户详情',
  73.         activeMenu: '/user/list'  // 高亮菜单
  74.       }
  75.     },
  76.     {
  77.       path: 'create',
  78.       name: 'UserCreate',
  79.       component: () => import('@/views/user/create.vue'),
  80.       meta: {
  81.         title: '新建用户',
  82.         activeMenu: '/user/list',
  83.         hidden: true  // 隐藏菜单
  84.       }
  85.     }
  86.   ]
  87. }
复制代码

路由守卫实现
  1. // src/router/guards.js
  2. import { useAuthStore } from '@/stores/auth'
  3. import { usePermissionStore } from '@/stores/permission'
  4. export default function setupGuards(router) {
  5.   // 全局前置守卫
  6.   router.beforeEach(async (to, from, next) => {
  7.     // 显示加载条
  8.     window.$loadingBar?.start()
  9.    
  10.     // 设置页面标题
  11.     document.title = to.meta.title ? `${to.meta.title} - 应用名称` : '应用名称'
  12.    
  13.     const authStore = useAuthStore()
  14.     const permissionStore = usePermissionStore()
  15.    
  16.     // 检查是否需要登录
  17.     if (to.meta.requiresAuth && !authStore.isAuthenticated) {
  18.       next({
  19.         path: '/login',
  20.         query: { redirect: to.fullPath }
  21.       })
  22.       return
  23.     }
  24.    
  25.     // 已登录但访问登录页,重定向到首页
  26.     if (to.path === '/login' && authStore.isAuthenticated) {
  27.       next({ path: '/' })
  28.       return
  29.     }
  30.    
  31.     // 检查权限
  32.     if (to.meta.permission && !permissionStore.hasPermission(to.meta.permission)) {
  33.       next({ path: '/403' })
  34.       return
  35.     }
  36.    
  37.     // 动态添加路由
  38.     if (authStore.isAuthenticated && !permissionStore.isRoutesAdded) {
  39.       try {
  40.         await permissionStore.generateRoutes()
  41.         permissionStore.routes.forEach(route => {
  42.           router.addRoute(route)
  43.         })
  44.         permissionStore.setRoutesAdded(true)
  45.         
  46.         // 确保addRoutes已完成
  47.         next({ ...to, replace: true })
  48.         return
  49.       } catch (error) {
  50.         console.error('添加路由失败:', error)
  51.         next('/login')
  52.         return
  53.       }
  54.     }
  55.    
  56.     next()
  57.   })
  58.   
  59.   // 全局解析守卫
  60.   router.beforeResolve(async (to) => {
  61.     // 预取数据
  62.     if (to.meta.dataPreFetch) {
  63.       try {
  64.         await to.meta.dataPreFetch(to)
  65.       } catch (error) {
  66.         console.error('数据预取失败:', error)
  67.         return false
  68.       }
  69.     }
  70.   })
  71.   
  72.   // 全局后置钩子
  73.   router.afterEach(() => {
  74.     // 结束加载条
  75.     window.$loadingBar?.finish()
  76.   })
  77.   
  78.   // 路由错误处理
  79.   router.onError((error) => {
  80.     console.error('路由错误:', error)
  81.     window.$loadingBar?.error()
  82.   })
  83. }
复制代码

权限控制实现
  1. // src/stores/permission.js
  2. import { defineStore } from 'pinia'
  3. import { constantRoutes, asyncRoutes } from '@/router'
  4. import { useAuthStore } from './auth'
  5. export const usePermissionStore = defineStore('permission', {
  6.   state: () => ({
  7.     routes: [],
  8.     addRoutes: [],
  9.     isRoutesAdded: false,
  10.     permissions: []
  11.   }),
  12.   
  13.   getters: {
  14.     hasPermission: (state) => (permission) => {
  15.       return state.permissions.includes(permission)
  16.     },
  17.    
  18.     menuRoutes: (state) => {
  19.       return state.routes.filter(route => {
  20.         return !route.meta?.hidden
  21.       })
  22.     }
  23.   },
  24.   
  25.   actions: {
  26.     setRoutes(routes) {
  27.       this.addRoutes = routes
  28.       this.routes = constantRoutes.concat(routes)
  29.     },
  30.    
  31.     setRoutesAdded(added) {
  32.       this.isRoutesAdded = added
  33.     },
  34.    
  35.     setPermissions(permissions) {
  36.       this.permissions = permissions
  37.     },
  38.    
  39.     async generateRoutes() {
  40.       const authStore = useAuthStore()
  41.       
  42.       // 从后端获取用户权限
  43.       const { permissions } = await authStore.fetchUserPermissions()
  44.       this.setPermissions(permissions)
  45.       
  46.       // 根据权限过滤路由
  47.       const accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions)
  48.       
  49.       this.setRoutes(accessedRoutes)
  50.       return accessedRoutes
  51.     }
  52.   }
  53. })
  54. // 根据权限过滤路由
  55. function filterAsyncRoutes(routes, permissions) {
  56.   const res = []
  57.   
  58.   routes.forEach(route => {
  59.     const tmp = { ...route }
  60.    
  61.     if (hasPermission(permissions, tmp)) {
  62.       if (tmp.children) {
  63.         tmp.children = filterAsyncRoutes(tmp.children, permissions)
  64.       }
  65.       res.push(tmp)
  66.     }
  67.   })
  68.   
  69.   return res
  70. }
  71. // 检查路由权限
  72. function hasPermission(permissions, route) {
  73.   if (route.meta && route.meta.permission) {
  74.     return permissions.includes(route.meta.permission)
  75.   } else {
  76.     return true
  77.   }
  78. }
复制代码

性能优化策略
  1. // 使用webpackChunkName进行代码分割
  2. const routes = [
  3.   {
  4.     path: '/dashboard',
  5.     name: 'Dashboard',
  6.     component: () => import(/* webpackChunkName: "dashboard" */ '@/views/dashboard/index.vue')
  7.   },
  8.   {
  9.     path: '/user',
  10.     name: 'User',
  11.     component: () => import(/* webpackChunkName: "user" */ '@/views/user/index.vue'),
  12.     children: [
  13.       {
  14.         path: 'list',
  15.         name: 'UserList',
  16.         component: () => import(/* webpackChunkName: "user" */ '@/views/user/list.vue')
  17.       },
  18.       {
  19.         path: 'detail/:id',
  20.         name: 'UserDetail',
  21.         component: () => import(/* webpackChunkName: "user" */ '@/views/user/detail.vue')
  22.       }
  23.     ]
  24.   }
  25. ]
复制代码
  1. // 在应用初始化后预加载关键路由
  2. import router from '@/router'
  3. function preloadCriticalRoutes() {
  4.   // 预加载用户可能访问的路由
  5.   const criticalRoutes = ['/dashboard', '/user/list']
  6.   
  7.   criticalRoutes.forEach(path => {
  8.     const route = router.resolve(path)
  9.     if (route.matched.length) {
  10.       route.matched.forEach(record => {
  11.         if (record.components.default) {
  12.           record.components.default()
  13.         }
  14.       })
  15.     }
  16.   })
  17. }
  18. // 在应用挂载后调用
  19. app.mount('#app')
  20. preloadCriticalRoutes()
复制代码
  1. // 使用keep-alive缓存组件
  2. <template>
  3.   <router-view v-slot="{ Component, route }">
  4.     <keep-alive :include="cachedViews">
  5.       <component :is="Component" :key="route.fullPath" />
  6.     </keep-alive>
  7.   </router-view>
  8. </template>
  9. <script>
  10. export default {
  11.   data() {
  12.     return {
  13.       cachedViews: ['DashboardHome', 'Analysis'] // 需要缓存的组件名称
  14.     }
  15.   },
  16.   watch: {
  17.     $route(to) {
  18.       if (to.meta.keepAlive) {
  19.         if (!this.cachedViews.includes(to.name)) {
  20.           this.cachedViews.push(to.name)
  21.         }
  22.       }
  23.     }
  24.   }
  25. }
  26. </script>
复制代码

最佳实践和常见问题解决

路由设计最佳实践

1. 扁平化路由结构:尽量保持路由结构扁平,避免过深的嵌套。
2. 一致的命名约定:使用一致的命名约定,如使用kebab-case命名路由路径,使用PascalCase命名组件。
3. 合理使用路由元信息:将路由相关的配置信息放在meta字段中,如权限、标题、缓存等。
4. 模块化路由:将路由按功能模块划分,提高可维护性。
5. 懒加载:所有非首屏路由都应使用懒加载,减少初始加载体积。

扁平化路由结构:尽量保持路由结构扁平,避免过深的嵌套。

一致的命名约定:使用一致的命名约定,如使用kebab-case命名路由路径,使用PascalCase命名组件。

合理使用路由元信息:将路由相关的配置信息放在meta字段中,如权限、标题、缓存等。

模块化路由:将路由按功能模块划分,提高可维护性。

懒加载:所有非首屏路由都应使用懒加载,减少初始加载体积。

常见问题解决
  1. // 问题:路由参数变化但组件复用,导致数据不更新
  2. // 解决方案1:监听路由参数变化
  3. export default {
  4.   setup() {
  5.     const route = useRoute()
  6.    
  7.     watch(() => route.params.id, (newId) => {
  8.       fetchData(newId)
  9.     }, { immediate: true })
  10.   }
  11. }
  12. // 解决方案2:使用key强制刷新组件
  13. <router-view :key="$route.fullPath" />
复制代码
  1. // 问题:动态添加路由后,立即导航到新路由不生效
  2. // 解决方案:使用next({ ...to, replace: true })重新导航
  3. router.beforeEach(async (to, from, next) => {
  4.   if (needsToAddRoutes) {
  5.     await addRoutes()
  6.     next({ ...to, replace: true })
  7.   } else {
  8.     next()
  9.   }
  10. })
复制代码
  1. // 问题:在路由守卫中执行异步操作,导致导航行为异常
  2. // 解决方案:确保在所有异步操作完成后再调用next
  3. router.beforeEach(async (to, from, next) => {
  4.   try {
  5.     await checkAuth()
  6.     await fetchPermissions()
  7.     next()
  8.   } catch (error) {
  9.     next('/login')
  10.   }
  11. })
复制代码
  1. // 问题:路由切换时滚动位置不符合预期
  2. // 解决方案:配置路由滚动行为
  3. const router = createRouter({
  4.   history: createWebHistory(),
  5.   routes,
  6.   scrollBehavior(to, from, savedPosition) {
  7.     if (savedPosition) {
  8.       // 使用浏览器的前进/后退按钮时恢复滚动位置
  9.       return savedPosition
  10.     } else if (to.hash) {
  11.       // 如果有锚点,滚动到锚点位置
  12.       return {
  13.         el: to.hash,
  14.         behavior: 'smooth'
  15.       }
  16.     } else {
  17.       // 否则滚动到页面顶部
  18.       return { top: 0 }
  19.     }
  20.   }
  21. })
复制代码

总结

Vue3和Vue Router为我们提供了构建现代单页应用的强大能力。通过本文的介绍,我们深入了解了Vue Router的高级特性,包括动态路由、嵌套路由、导航守卫等,并学习了如何构建可扩展、高性能的前端路由架构。

在实际项目中,我们应该根据应用规模和需求选择合适的路由设计方案,遵循模块化、可扩展的原则,合理使用路由懒加载、权限控制、数据预取等技术,以提供更好的用户体验和开发体验。

随着Vue3和Vue Router的不断发展,我们还需要持续关注新特性和最佳实践,不断优化我们的路由架构,以适应不断变化的前端开发需求。

希望本文能够帮助你深入理解Vue3 Vue Router的高级用法,并在实际项目中构建出可扩展、高性能的前端路由架构。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则