前言
Vue Router 是 Vue 生态中处理页面跳转的核心工具,它解决了单页应用中 URL 管理、组件切换、状态维护等关键问题,同时提供了丰富的功能(如动态路由、嵌套路由、路由守卫)。除了经常用到的路由配置以外,我们还需了解以下几点。
1. Vue 路由模式
Vue Router 支持两种路由模式:
模式 | 原理 | 特点 | 启用方式 |
---|---|---|---|
Hash 模式 | 使用 URL hash (# ) 模拟完整 URL http://example.com/#/home |
兼容性好(支持 IE9) 无需服务器配置 | mode: 'hash' (默认) |
History 模式 | 基于 HTML5 History API http://example.com/home |
URL 更美观 需要服务器端支持 | mode: 'history' |
服务器配置示例 (History 模式):
# Nginx 配置
location / {
try_files $uri $uri/ /index.html;
}
2. 路由懒加载
实现组件按需加载,提升首屏性能。
实现方式:
const router = new VueRouter({
routes: [
// 魔法注释:webpackChunkName 定义分包名称
{
path: '/dashboard',
component: () => import(/* webpackChunkName: "dashboard"*/'./views/Dashboard.vue')
},
{
path: '/user/:id',
component: () => import(/* webpackChunkName: "user" */ './views/UserProfile.vue')
}
]
})
优化效果:
- 首屏加载时间减少 30-50%
- 按路由拆分 chunk 文件
- 支持预加载:
<link rel="prefetch">
3. 路由缓存
使用 <keep-alive>
缓存组件状态:
<template>
<div id="app">
<!-- 缓存带有 meta.keepAlive 的路由 -->
<keep-alive :include="cachedViews">
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<!-- 不缓存的路由 -->
<router-view v-if="!$route.meta.keepAlive"></router-view>
</div>
</template>
<script>
export default {
computed: {
cachedViews() {
return this.$store.state.cachedRoutes
}
}
}
</script>
路由配置:
{
path: '/user/:id',
component: UserDetails,
meta: {
keepAlive: true, // 启用缓存
scrollPos: true // 记录滚动位置
}
}
可通过属性控制缓存范围:
include
:仅缓存名称匹配的组件(组件需定义name
属性)。exclude
:不缓存名称匹配的组件。max
:最多缓存的组件实例数量。
示例:
<keep-alive include="Home,About" :max="10">
<router-view />
</keep-alive>
被缓存的组件会触发 activated
(激活时)和 deactivated
(失活时)生命周期钩子,替代 mounted
和 unmounted
。
4. 路由守卫
守卫类型:
// 1. 全局前置守卫
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isLoggedIn()) next('/login')
else next()
})
// 2. 路由独享守卫
{
path: '/admin',
beforeEnter: (to, from, next) => {
checkAdminPermission() ? next() : next('/403')
}
}
// 3. 组件内守卫
const UserProfile = {
beforeRouteEnter(to, from, next) {
// 不能访问 this
next(vm => console.log(vm.user)) // 通过回调访问组件实例
},
beforeRouteUpdate(to, from, next) {
// 路由参数变化时触发
this.fetchData(to.params.id)
next()
},
beforeRouteLeave(to, from, next) {
// 离开确认
window.onbeforeunload = null
next()
}
}
1.全局路由
Route.beforeEeach 任意路由跳转前触发
Route.beforeResolve 导航确认前触发
Route.afterEach 导航完成后触发
2.路由独享守卫
beforeEnter 在路由配置上定义,进入路由时触发
3.组件内守卫
beforeRouteEnter 渲染组件路由被验证前调用(适合做进入前的权限验证,不能访问this)
beforeRouteUpdate 当组件路由改变,组件复用时候调用(适合在路由参数变化时重新加载数据
beforeRouteLeave 导航离开组件的对应路由时调用(适合做离开前的确认(如未保存的表单)
导航解析流程:
- 1.触发导航 → 生成目标路由
- 2.在失活的组件里调用(
beforeRouteLeave
) - 3.执行 全局前置守卫(
beforeEach
) - 4.在重用的组件里调用beforeRouteUpdate
- 5.执行 路由独享守卫(
beforeEnter
) - 6.执行 组件内前置守卫(
beforeRouteEnter
) - 7.解析异步路由组件:如
() => import('./User.vue')
- 8.执行 组件内更新守卫(
beforeRouteUpdate
,如适用) - 9.执行 组件内离开守卫(
beforeRouteLeave
,如适用) - 10.执行 全局解析守卫(
beforeResolve
) - 11.确认导航,更新 URL 和路由记录
- 12.执行 全局后置钩子(
afterEach
) - 13.Dom更新:销毁旧组件,创建并渲染新组件 → 完成导航
通过这个流程,Vue Router 实现了对路由导航的精细控制,结合不同类型的守卫可以满足权限验证、数据预加载、离开确认等多种业务需求。
5. 路由与后端菜单结合
动态路由工作流:
实现代码:
// 1. 定义基础路由(无需权限)
const constantRoutes = [
{ path: '/login', component: Login },
{ path: '/404', component: NotFound }
]
// 2. 从后端获取菜单
async function initRoutes() {
const menuData = await axios.get('/api/user/menus')
const dynamicRoutes = generateRoutes(menuData)
// 3. 动态添加路由
dynamicRoutes.forEach(route => router.addRoute(route))
// 4. 添加404捕获
router.addRoute({ path: '*', redirect: '/404' })
}
// 菜单转换函数
function generateRoutes(menuData) {
return menuData.map(menu => ({
path: menu.path,
component: () => import(`@/views/${menu.component}`),
meta: { title: menu.title, icon: menu.icon },
children: menu.children ? generateRoutes(menu.children) : []
}))
}
6. 路由权限控制
完整权限方案:
// 权限检查函数
function hasPermission(route, roles) {
if (route.meta?.roles) {
return roles.some(role => route.meta.roles.includes(role))
}
return true // 无meta.roles则公开访问
}
// 路由过滤
function filterRoutes(routes, roles) {
return routes.filter(route => {
if (hasPermission(route, roles)) {
if (route.children) {
route.children = filterRoutes(route.children, roles)
}
return true
}
return false
})
}
// 全局前置守卫
router.beforeEach(async (to, from, next) => {
// 1. 获取用户角色
const roles = store.getters.roles || []
// 2. 白名单检查
if (whiteList.includes(to.path)) return next()
// 3. 未登录重定向
if (!roles.length) return next(`/login?redirect=${to.path}`)
// 4. 已登录但未初始化路由
if (!store.getters.routesLoaded) {
await initDynamicRoutes(roles) // 动态添加路由
return next({ ...to, replace: true })
}
// 5. 检查目标路由权限
if (!hasPermission(to, roles)) return next('/403')
next()
})
按钮级权限控制:
<template>
<button v-permission="'user:create'">添加用户</button>
</template>
// 权限指令
Vue.directive('permission', {
inserted(el, binding) {
const { value } = binding
const permissions = store.getters.permissions
if (value && !permissions.includes(value)) {
el.parentNode?.removeChild(el)
}
}
})
最佳实践总结
- 路由设计:
- 公共路由使用静态注册
- 权限路由使用动态注册
- 路由元信息存储权限标识
- 性能优化:
- 路由懒加载 + webpack 分包
- 滚动行为恢复
- 路由组件复用
- 安全控制:
- 前端路由权限校验
- 后端接口二次验证
- 按钮级权限控制
- 错误处理:
- 统一404处理
- 403无权限页面
- 路由切换错误捕获
// 错误捕获
router.onError(error => {
console.error('路由错误:', error)
if (/ChunkLoadError/.test(error.message)) {
window.location.reload() // 重新加载解决chunk加载失败
}
})