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. 导航守卫类型与执行流程
完整的导航解析流程:
- 导航被触发
- 调用
beforeRouteLeave
守卫(组件内) - 调用全局
beforeEach
守卫 - 在重用的组件里调用
beforeRouteUpdate
守卫(组件内) - 调用路由配置里的
beforeEnter
守卫 - 解析异步路由组件
- 在被激活的组件里调用
beforeRouteEnter
(组件内) - 调用全局
beforeResolve
守卫 - 导航被确认
- 调用全局
afterEach
钩子 - 触发DOM更新
- 调用
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配置示例:
nginxlocation / { try_files $uri $uri/ /index.html; }
问题3:路由组件不更新
javascript
// 使用beforeRouteUpdate或监听$route
watch(
() => route.params.id,
(newId) => {
fetchData(newId)
}
)
2. 最佳实践建议
-
路由组织:
- 按功能模块组织路由文件
- 使用路由元信息(meta)存储权限、标题等信息
- 对大型项目考虑自动导入路由
-
性能优化:
- 合理使用路由懒加载
- 对频繁访问的路由考虑预加载
- 避免在导航守卫中进行繁重操作
-
安全实践:
- 始终验证前端路由权限
- 敏感路由应在后端再次验证
- 使用路由独享守卫处理特殊权限
-
开发体验:
- 为路由添加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的核心概念和高级用法。从基础配置到导航守卫,从动态路由到状态集成,这些知识将帮助您构建复杂且高效的单页应用程序。实际项目中应根据具体需求选择合适的路由方案,并遵循最佳实践以确保应用的性能和可维护性。