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

相关推荐
福兮说1 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
郑州光合科技余经理2 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
专业程序开发源2 小时前
springboot简历管理系统81389-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·php·课程设计
专业程序开发源3 小时前
springboot社区养老系统44071-计算机课程设计、毕业设计
vue.js·spring boot·后端·python·django·php·课程设计
小呆呆6663 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
凤城老人3 小时前
从 PyQt6 到 Electron:给 Edge TTS 做一个“多角色配音机“的踩坑手记
javascript·typescript·electron
Dovis(誓平步青云)3 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan4 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_4 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
IT_陈寒4 小时前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端