Vue路由深度解析:Vue Router与导航守卫

发布于:2025-05-20 ⋅ 阅读:(20) ⋅ 点赞:(0)

Vue路由深度解析:Vue Router与导航守卫

一、Vue Router基础与安装配置

1. Vue Router核心概念

Vue Router是Vue.js官方的路由管理器,主要功能包括:

  • 嵌套路由映射
  • 模块化的路由配置
  • 路由参数、查询、通配符
  • 细粒度的导航控制
  • 自动激活的CSS类链接
  • HTML5 history模式或hash模式
  • 可定制的滚动行为

2. 安装与基本配置

安装Vue Router

npm install vue-router@4
# 或
yarn add vue-router@4

基础配置示例

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    // 路由级代码拆分
    component: () => import('../views/AboutView.vue')
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

在main.js中引入

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(router)
app.mount('#app')

二、路由模式与路由配置详解

1. 路由模式对比

模式 特点 示例
Hash模式 使用URL hash模拟完整URL http://example.com/#/about
History模式 使用HTML5 History API http://example.com/about
Memory模式 不修改URL,适合非浏览器环境(如Electron) 无URL变化

配置示例

import { 
  createWebHashHistory,  // Hash模式
  createWebHistory,     // History模式
  createMemoryHistory   // Memory模式
} from 'vue-router'

const router = createRouter({
  history: createWebHistory(), // 推荐生产环境使用
  // history: createWebHashHistory(), // 兼容性更好
  // history: createMemoryHistory(), // 非浏览器环境
  routes
})

2. 动态路由与参数传递

动态路由配置

const routes = [
  // 动态字段以冒号开始
  {
    path: '/user/:id',
    name: 'user',
    component: UserView
  },
  // 可匹配/user/123或/user/456
  {
    path: '/user/:id/posts/:postId',
    component: UserPostView
  }
]

参数获取方式

<template>
  <!-- 在模板中直接使用 -->
  <div>用户ID: {{ $route.params.id }}</div>
</template>

<script setup>
import { useRoute } from 'vue-router'

// 在setup中使用
const route = useRoute()
console.log(route.params.id)
</script>

<script>
// 在Options API中使用
export default {
  created() {
    console.log(this.$route.params.id)
  }
}
</script>

3. 嵌套路由与命名视图

嵌套路由配置

const routes = [
  {
    path: '/user/:id',
    component: UserLayout,
    children: [
      {
        path: '', // 默认子路由
        component: UserProfile
      },
      {
        path: 'posts',
        component: UserPosts
      },
      {
        path: 'settings',
        component: UserSettings
      }
    ]
  }
]

命名视图(多路由出口)

const routes = [
  {
    path: '/',
    components: {
      default: HomeView, // 默认出口
      sidebar: SidebarView, // <router-view name="sidebar">
      footer: AppFooter  // <router-view name="footer">
    }
  }
]

三、导航守卫全面解析

1. 导航守卫类型与执行流程

完整的导航解析流程

  1. 导航被触发
  2. 调用beforeRouteLeave守卫(组件内)
  3. 调用全局beforeEach守卫
  4. 在重用的组件里调用beforeRouteUpdate守卫(组件内)
  5. 调用路由配置里的beforeEnter守卫
  6. 解析异步路由组件
  7. 在被激活的组件里调用beforeRouteEnter(组件内)
  8. 调用全局beforeResolve守卫
  9. 导航被确认
  10. 调用全局afterEach钩子
  11. 触发DOM更新
  12. 调用beforeRouteEnter守卫中传给next的回调函数

2. 全局守卫

// router/index.js

// 全局前置守卫
router.beforeEach((to, from, next) => {
  console.log('全局前置守卫', to, from)
  // 必须调用next()继续导航
  next()
})

// 全局解析守卫
router.beforeResolve((to, from, next) => {
  console.log('全局解析守卫', to, from)
  next()
})

// 全局后置钩子
router.afterEach((to, from) => {
  console.log('全局后置钩子', to, from)
  // 不需要next函数
})

3. 路由独享守卫

const routes = [
  {
    path: '/admin',
    component: AdminView,
    beforeEnter: (to, from, next) => {
      // 仅在此路由触发
      if (isAdmin()) next()
      else next('/login')
    }
  }
]

4. 组件内守卫

<script>
export default {
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this`
    next(vm => {
      // 通过 `vm` 访问组件实例
      console.log(vm.someData)
    })
  },
  beforeRouteUpdate(to, from) {
    // 当前路由改变,但是该组件被复用时调用
    // 可以访问组件实例 `this`
    this.fetchData(to.params.id)
  },
  beforeRouteLeave(to, from) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
    const answer = window.confirm('确定要离开吗?未保存的更改将会丢失')
    if (!answer) return false
  }
}
</script>

5. 导航守卫实战:权限控制

// router/index.js
import { useAuthStore } from '@/stores/auth'

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView,
    meta: { requiresAuth: false }
  },
  {
    path: '/dashboard',
    name: 'dashboard',
    component: DashboardView,
    meta: { 
      requiresAuth: true,
      roles: ['admin', 'editor'] 
    }
  },
  {
    path: '/admin',
    name: 'admin',
    component: AdminView,
    meta: { 
      requiresAuth: true,
      roles: ['admin'] 
    }
  }
]

router.beforeEach(async (to, from, next) => {
  const authStore = useAuthStore()
  const isAuthenticated = authStore.isAuthenticated
  const userRole = authStore.user?.role || 'guest'
  
  // 检查路由是否需要认证
  if (to.meta.requiresAuth && !isAuthenticated) {
    return next({ name: 'login', query: { redirect: to.fullPath } })
  }
  
  // 检查路由角色权限
  if (to.meta.roles && !to.meta.roles.includes(userRole)) {
    return next({ name: 'forbidden' })
  }
  
  // 如果用户已登录但要去登录页,重定向到首页
  if (to.name === 'login' && isAuthenticated) {
    return next({ name: 'home' })
  }
  
  next()
})

四、路由高级特性

1. 路由元信息与过渡动画

路由元信息配置

const routes = [
  {
    path: '/posts',
    component: PostsLayout,
    meta: { 
      requiresAuth: true,
      transition: 'slide-left' 
    },
    children: [
      {
        path: 'new',
        component: NewPost,
        meta: { 
          transition: 'slide-up',
          requiresAdmin: true 
        }
      }
    ]
  }
]

动态过渡效果

<template>
  <router-view v-slot="{ Component, route }">
    <transition :name="route.meta.transition || 'fade'">
      <component :is="Component" />
    </transition>
  </router-view>
</template>

<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

.slide-left-enter-active,
.slide-left-leave-active {
  transition: transform 0.3s ease;
}

.slide-left-enter-from {
  transform: translateX(100%);
}

.slide-left-leave-to {
  transform: translateX(-100%);
}

.slide-up-enter-active,
.slide-up-leave-active {
  transition: transform 0.3s ease;
}

.slide-up-enter-from {
  transform: translateY(100%);
}

.slide-up-leave-to {
  transform: translateY(-100%);
}
</style>

2. 滚动行为控制

const router = createRouter({
  history: createWebHistory(),
  routes,
  scrollBehavior(to, from, savedPosition) {
    // 返回滚动位置对象
    if (savedPosition) {
      return savedPosition // 浏览器前进/后退时恢复位置
    }
    if (to.hash) {
      return {
        el: to.hash, // 滚动到锚点
        behavior: 'smooth' // 平滑滚动
      }
    }
    if (to.meta.scrollToTop) {
      return { top: 0 } // 新路由滚动到顶部
    }
    // 默认不改变滚动位置
  }
})

3. 路由懒加载与分包

基础懒加载

const routes = [
  {
    path: '/about',
    component: () => import('../views/AboutView.vue')
  }
]

自定义分包

const routes = [
  {
    path: '/admin',
    component: () => import(/* webpackChunkName: "admin" */ '../views/AdminView.vue'),
    children: [
      {
        path: 'dashboard',
        component: () => import(/* webpackChunkName: "admin" */ '../views/AdminDashboard.vue')
      }
    ]
  },
  {
    path: '/user/:id',
    component: () => import(/* webpackChunkName: "user" */ '../views/UserView.vue')
  }
]

4. 动态路由API

添加路由

router.addRoute({
  path: '/new-route',
  name: 'newRoute',
  component: () => import('../views/NewView.vue')
})

// 添加到现有路由的子路由
router.addRoute('parentRoute', {
  path: 'child',
  component: () => import('../views/ChildView.vue')
})

删除路由

// 通过名称删除
router.removeRoute('routeName')

// 通过添加返回的回调删除
const removeRoute = router.addRoute(routeConfig)
removeRoute() // 删除路由

检查路由

// 检查路由是否存在
router.hasRoute('routeName')

// 获取所有路由记录
router.getRoutes()

五、常见问题与最佳实践

1. 常见问题解决方案

问题1:路由重复跳转报错

// 统一处理导航错误
router.onError((error) => {
  if (error.message.includes('Avoided redundant navigation')) {
    // 忽略重复导航错误
  } else {
    // 处理其他导航错误
  }
})

问题2:动态路由刷新404

  • History模式需要服务器配置支持
  • Nginx配置示例:
    location / {
      try_files $uri $uri/ /index.html;
    }
    

问题3:路由组件不更新

// 使用beforeRouteUpdate或监听$route
watch(
  () => route.params.id,
  (newId) => {
    fetchData(newId)
  }
)

2. 最佳实践建议

  1. 路由组织

    • 按功能模块组织路由文件
    • 使用路由元信息(meta)存储权限、标题等信息
    • 对大型项目考虑自动导入路由
  2. 性能优化

    • 合理使用路由懒加载
    • 对频繁访问的路由考虑预加载
    • 避免在导航守卫中进行繁重操作
  3. 安全实践

    • 始终验证前端路由权限
    • 敏感路由应在后端再次验证
    • 使用路由独享守卫处理特殊权限
  4. 开发体验

    • 为路由添加name属性方便跳转
    • 使用路由元信息管理页面标题
    • 实现进度条提升用户体验

六、综合实战:企业级路由方案

1. 完整路由配置示例

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import NProgress from 'nprogress'

const routes = [
  {
    path: '/',
    name: 'home',
    component: () => import('@/views/HomeView.vue'),
    meta: {
      title: '首页',
      requiresAuth: false,
      cache: true
    }
  },
  {
    path: '/login',
    name: 'login',
    component: () => import('@/views/LoginView.vue'),
    meta: {
      title: '登录',
      guestOnly: true
    }
  },
  {
    path: '/dashboard',
    name: 'dashboard',
    component: () => import('@/views/DashboardView.vue'),
    meta: {
      title: '仪表盘',
      requiresAuth: true
    }
  },
  {
    path: '/admin',
    name: 'admin',
    component: () => import('@/views/layouts/AdminLayout.vue'),
    meta: {
      title: '管理后台',
      requiresAuth: true,
      roles: ['admin']
    },
    children: [
      {
        path: '',
        name: 'admin-dashboard',
        component: () => import('@/views/admin/DashboardView.vue'),
        meta: { title: '控制台' }
      },
      {
        path: 'users',
        name: 'admin-users',
        component: () => import('@/views/admin/UsersView.vue'),
        meta: { title: '用户管理' }
      }
    ]
  },
  {
    path: '/:pathMatch(.*)*',
    name: 'not-found',
    component: () => import('@/views/NotFoundView.vue'),
    meta: {
      title: '页面不存在'
    }
  }
]

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) return savedPosition
    if (to.hash) return { el: to.hash, behavior: 'smooth' }
    return { top: 0 }
  }
})

// 进度条配置
NProgress.configure({ showSpinner: false })

// 全局前置守卫
router.beforeEach(async (to, from, next) => {
  NProgress.start()
  
  const authStore = useAuthStore()
  const isAuthenticated = authStore.isAuthenticated
  const userRole = authStore.user?.role || 'guest'
  
  // 设置页面标题
  document.title = to.meta.title ? `${to.meta.title} | 我的应用` : '我的应用'
  
  // 检查认证
  if (to.meta.requiresAuth && !isAuthenticated) {
    return next({
      name: 'login',
      query: { redirect: to.fullPath }
    })
  }
  
  // 检查角色权限
  if (to.meta.roles && !to.meta.roles.includes(userRole)) {
    return next({ name: 'forbidden' })
  }
  
  // 已登录用户访问guestOnly路由
  if (to.meta.guestOnly && isAuthenticated) {
    return next({ name: 'home' })
  }
  
  next()
})

// 全局后置钩子
router.afterEach(() => {
  NProgress.done()
})

export default router

2. 路由工具函数

// utils/router.js
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // 重置路由
}

export function loadRoutesByRole(role) {
  const dynamicRoutes = []
  
  if (role === 'admin') {
    dynamicRoutes.push(
      {
        path: '/admin',
        component: () => import('@/views/AdminView.vue'),
        children: [
          // 管理员专属路由
        ]
      }
    )
  }
  
  dynamicRoutes.forEach(route => {
    router.addRoute(route)
  })
}

export function getRouteTitle(route) {
  return route.meta.title || ''
}

3. 路由与状态管理集成

// stores/app.js
import { defineStore } from 'pinia'
import { useRouter } from 'vue-router'

export const useAppStore = defineStore('app', {
  state: () => ({
    cachedViews: [],
    visitedViews: []
  }),
  actions: {
    addCachedView(view) {
      if (this.cachedViews.includes(view.name)) return
      if (view.meta?.cache) {
        this.cachedViews.push(view.name)
      }
    },
    addVisitedView(view) {
      const existing = this.visitedViews.find(v => v.path === view.path)
      if (existing) {
        if (existing.fullPath !== view.fullPath) {
          // 更新现有记录
          Object.assign(existing, view)
        }
        return
      }
      this.visitedViews.push(
        Object.assign({}, view, {
          title: view.meta?.title || '未知'
        })
      )
    },
    async logout() {
      const router = useRouter()
      // 清理状态
      this.$reset()
      // 重定向到登录页
      await router.push('/login')
    }
  }
})

通过本指南,您已经全面掌握了Vue Router的核心概念和高级用法。从基础配置到导航守卫,从动态路由到状态集成,这些知识将帮助您构建复杂且高效的单页应用程序。实际项目中应根据具体需求选择合适的路由方案,并遵循最佳实践以确保应用的性能和可维护性。


网站公告

今日签到

点亮在社区的每一天
去签到