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

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

一、Vue Router基础与安装配置

1. Vue Router核心概念

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

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

2. 安装与基本配置

安装Vue Router

bash 复制代码
npm install vue-router@4
# 或
yarn add vue-router@4

基础配置示例

javascript 复制代码
// 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中引入

javascript 复制代码
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变化

配置示例

javascript 复制代码
import { 
  createWebHashHistory,  // Hash模式
  createWebHistory,     // History模式
  createMemoryHistory   // Memory模式
} from 'vue-router'

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

2. 动态路由与参数传递

动态路由配置

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

参数获取方式

vue 复制代码
<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. 嵌套路由与命名视图

嵌套路由配置

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

命名视图(多路由出口)

javascript 复制代码
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. 全局守卫

javascript 复制代码
// 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. 路由独享守卫

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

4. 组件内守卫

vue 复制代码
<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. 导航守卫实战:权限控制

javascript 复制代码
// 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. 路由元信息与过渡动画

路由元信息配置

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

动态过渡效果

vue 复制代码
<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. 滚动行为控制

javascript 复制代码
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. 路由懒加载与分包

基础懒加载

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

自定义分包

javascript 复制代码
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

添加路由

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

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

删除路由

javascript 复制代码
// 通过名称删除
router.removeRoute('routeName')

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

检查路由

javascript 复制代码
// 检查路由是否存在
router.hasRoute('routeName')

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

五、常见问题与最佳实践

1. 常见问题解决方案

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

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

问题2:动态路由刷新404

  • History模式需要服务器配置支持

  • Nginx配置示例:

    nginx 复制代码
    location / {
      try_files $uri $uri/ /index.html;
    }

问题3:路由组件不更新

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

2. 最佳实践建议

  1. 路由组织

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

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

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

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

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

1. 完整路由配置示例

javascript 复制代码
// 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. 路由工具函数

javascript 复制代码
// 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. 路由与状态管理集成

javascript 复制代码
// 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的核心概念和高级用法。从基础配置到导航守卫,从动态路由到状态集成,这些知识将帮助您构建复杂且高效的单页应用程序。实际项目中应根据具体需求选择合适的路由方案,并遵循最佳实践以确保应用的性能和可维护性。

相关推荐
koiy.cc34 分钟前
记录:echarts实现tooltip的某个数据常显和恢复
前端·echarts
一只专注api接口开发的技术猿44 分钟前
企业级电商数据对接:1688 商品详情 API 接口开发与优化实践
大数据·前端·爬虫
GISer_Jing1 小时前
[前端高频]数组转树、数组扁平化、深拷贝、JSON.stringify&JSON.parse等手撕
前端·javascript·json
古拉拉明亮之神1 小时前
Spark处理过程-转换算子
javascript·ajax·spark
Yvonne爱编码1 小时前
CSS- 4.1 浮动(Float)
前端·css·html·github·html5·hbuilder
timeguys2 小时前
【前端】[vue3] [uni-app]使用 vantUI 框架
前端·uni-app
岁岁岁平安2 小时前
Vue3学习(组合式API——Watch侦听器、watchEffect()详解)
前端·javascript·vue.js·学习·watch侦听器·组合式api
码视野2 小时前
基于Spring Boot和Vue的在线考试系统架构设计与实现(源码+论文+部署讲解等)
vue.js·spring boot·系统架构
uwvwko2 小时前
BUUCTF——web刷题第一页题解
android·前端·数据库·php·web·ctf