Vue 路由进阶:路由守卫权限控制、动态路由、懒加载、路由缓存

Vue 路由进阶:路由守卫权限控制、动态路由、懒加载、路由缓存

Hi,我是前端人类学

在 Vue 单页应用开发中,路由管理往往从“配置几个页面”演变为“构建一套复杂的访问控制系统”。随着应用规模的增长,权限控制、模块动态加载、性能优化和状态保持等问题会接踵而至。

本文将从路由守卫权限控制动态路由懒加载路由缓存四个维度,深入探讨 Vue Router 的进阶实践。

文章目录

    • 一、路由守卫:构建应用的权限防火墙
      • 1.1 全局守卫
      • 1.2 路由独享守卫
      • 1.3 组件内守卫
    • 二、动态路由:运行时注册路由
      • 2.1 路由初始化与动态添加
      • 2.2 路由删除与重置
      • 2.3 路由权限的精细化控制
    • 三、懒加载:路由级代码分割
      • 3.1 基本用法
      • 3.2 命名 chunk
      • 3.3 预加载与预获取
      • 3.4 加载状态与错误处理
    • 四、路由缓存:KeepAlive 与 RouterView
      • 4.1 基本缓存
      • 4.2 精细控制缓存
      • 4.3 基于路由动态决定缓存
      • 4.4 缓存下的生命周期
      • 4.5 标签页缓存方案
    • 五、综合实践:完整权限路由方案

一、路由守卫:构建应用的权限防火墙

路由守卫(Navigation Guards)是 Vue Router 提供的一套钩子函数,用于在路由跳转前后执行逻辑。它是实现权限控制的核心机制。

1.1 全局守卫

全局前置守卫router.beforeEach是最常用的权限控制入口,每次路由跳转前都会触发:

importrouterfrom'./router'router.beforeEach(async(to,from,next)=>{consttoken=localStorage.getItem('token')constuserStore=useUserStore()// 白名单:登录页、注册页等无需认证constwhiteList=['/login','/register']if(token){if(to.path==='/login'){// 已登录,跳转到首页next('/')}else{// 获取用户信息(仅首次)if(!userStore.userInfo){awaituserStore.fetchUserInfo()}next()}}else{if(whiteList.includes(to.path)){next()}else{// 未登录,跳转登录页并携带回跳地址next(`/login?redirect=${to.fullPath}`)}}})

全局解析守卫router.beforeResolve在导航确认前、组件渲染前触发,适用于在渲染前确保数据就绪。

全局后置钩子router.afterEach不接收next函数,适合日志记录、页面埋点等操作:

router.afterEach((to,from)=>{// 页面访问统计trackPageView(to.fullPath)// 更新页面标题document.title=to.meta.title||'默认标题'})

1.2 路由独享守卫

在路由配置中直接定义beforeEnter守卫,仅对该路由生效:

javascript

const routes = [ { path: '/admin', component: () => import('@/views/Admin.vue'), beforeEnter: (to, from, next) => { const role = localStorage.getItem('role') if (role === 'admin') { next() } else { next('/403') } } } ]

1.3 组件内守卫

在组件内部使用onBeforeRouteLeaveonBeforeRouteUpdate守卫:

<script setup>import{onBeforeRouteLeave,onBeforeRouteUpdate}from'vue-router'// 路由更新时(如 /posts/1 -> /posts/2)onBeforeRouteUpdate((to,from,next)=>{// 重新加载数据fetchPost(to.params.id)next()})// 离开当前页面时onBeforeRouteLeave((to,from,next)=>{if(formDataIsDirty.value){if(confirm('有未保存的内容,确定离开吗?')){next()}else{next(false)}}else{next()}})</script>

二、动态路由:运行时注册路由

动态路由是指应用在运行时根据用户权限、业务需求动态添加路由,而非在初始化时全部注册。这在 RBAC(基于角色的访问控制)系统中尤为重要。

2.1 路由初始化与动态添加

典型的动态路由流程:

// router/index.jsimport{createRouter,createWebHistory}from'vue-router'// 基础路由:登录页、404、白名单页面constconstantRoutes=[{path:'/login',component:()=>import('@/views/Login.vue')},{path:'/404',component:()=>import('@/views/404.vue')}]constrouter=createRouter({history:createWebHistory(),routes:constantRoutes})exportdefaultrouter

在全局守卫中动态添加权限路由:

// 异步路由配置(按角色划分)constasyncRouteMap={admin:[{path:'/dashboard',component:()=>import('@/views/Dashboard.vue')},{path:'/user-manage',component:()=>import('@/views/UserManage.vue')}],editor:[{path:'/dashboard',component:()=>import('@/views/Dashboard.vue')},{path:'/content',component:()=>import('@/views/Content.vue')}],guest:[{path:'/dashboard',component:()=>import('@/views/Dashboard.vue')}]}router.beforeEach(async(to,from,next)=>{consttoken=localStorage.getItem('token')if(token){if(!router.hasRoute('dashboard')){// 获取用户角色constrole=awaitgetUserRole()constasyncRoutes=asyncRouteMap[role]||[]// 动态添加路由asyncRoutes.forEach(route=>{router.addRoute(route)})// 添加 404 兜底(需放在最后)router.addRoute({path:'/:pathMatch(.*)*',redirect:'/404'})// 重新导航,确保动态路由生效returnnext({...to,replace:true})}next()}else{// 未登录处理...}})

2.2 路由删除与重置

在某些场景下(如退出登录),需要重置路由:

// 重置路由的方法exportfunctionresetRouter(){// 获取所有动态添加的路由constroutes=router.getRoutes()routes.forEach(route=>{// 保留基础路由(根据 name 过滤)if(route.name!=='Login'&&route.name!=='404'){router.removeRoute(route.name)}})}// 退出登录functionlogout(){resetRouter()localStorage.clear()router.push('/login')}

2.3 路由权限的精细化控制

除了控制路由是否存在,还可以通过路由元信息meta配合守卫实现更细粒度的权限控制:

constroutes=[{path:'/system',component:()=>import('@/views/System.vue'),meta:{roles:['admin','super_admin'],permissions:['system:read','system:write']}}]router.beforeEach((to,from,next)=>{constuserStore=useUserStore()// 检查角色if(to.meta.roles){consthasRole=to.meta.roles.some(role=>userStore.roles.includes(role))if(!hasRole)returnnext('/403')}// 检查权限if(to.meta.permissions){consthasPermission=to.meta.permissions.some(perm=>userStore.permissions.includes(perm))if(!hasPermission)returnnext('/403')}next()})

三、懒加载:路由级代码分割

在大型 SPA 中,首屏加载效率直接影响用户体验。路由懒加载通过代码分割将不同路由的代码拆分为独立的 chunk,只在访问对应路由时才加载。

3.1 基本用法

constroutes=[{path:'/dashboard',// 使用动态 importcomponent:()=>import('@/views/Dashboard.vue')},{path:'/settings',component:()=>import('@/views/Settings.vue')}]

3.2 命名 chunk

通过 Webpack/Vite 的魔法注释指定 chunk 名称,便于管理和缓存:

component:()=>import(/* webpackChunkName: "dashboard" */'@/views/Dashboard.vue')

3.3 预加载与预获取

结合路由元信息,实现智能预加载:

// 使用 webpackPrefetch 预获取(低优先级)component:()=>import(/* webpackChunkName: "dashboard" *//* webpackPrefetch: true */'@/views/Dashboard.vue')// 使用 webpackPreload 预加载(高优先级,适用于首屏关键路由)component:()=>import(/* webpackChunkName: "home" *//* webpackPreload: true */'@/views/Home.vue')

3.4 加载状态与错误处理

<template> <RouterView> <template #default="{ Component, route }"> <Suspense> <template #default> <component :is="Component" /> </template> <template #fallback> <div class="loading-spinner">加载中...</div> </template> </Suspense> </template> </RouterView> </template>

结合 Vue 的<Suspense>组件,可以优雅地处理异步组件的加载状态。

四、路由缓存:KeepAlive 与 RouterView

在多标签页或频繁切换的场景中,希望保留页面状态(如列表滚动位置、表单输入内容),避免重复渲染和接口请求。

4.1 基本缓存

<template> <RouterView v-slot="{ Component }"> <KeepAlive> <component :is="Component" /> </KeepAlive> </RouterView> </template>

4.2 精细控制缓存

通过includeexclude属性控制哪些路由组件需要缓存:

<template> <RouterView v-slot="{ Component }"> <KeepAlive :include="['Dashboard', 'UserList']" :max="5"> <component :is="Component" /> </KeepAlive> </RouterView> </template>

4.3 基于路由动态决定缓存

利用路由元信息动态决定是否缓存:

<template> <RouterView v-slot="{ Component, route }"> <KeepAlive :include="cachedViews"> <component :is="Component" :key="route.fullPath" /> </KeepAlive> </RouterView> </template> <script setup> import { ref, watch } from 'vue' import { useRoute } from 'vue-router' const cachedViews = ref([]) const route = useRoute() // 根据路由 meta 动态添加缓存 watch(() => route.meta.keepAlive, (newVal) => { if (newVal && !cachedViews.value.includes(route.name)) { cachedViews.value.push(route.name) } }, { immediate: true }) </script>

4.4 缓存下的生命周期

<KeepAlive>缓存的组件会经历activateddeactivated生命周期钩子:

<script setup>import{onActivated,onDeactivated}from'vue'// 组件从缓存中激活时执行onActivated(()=>{// 刷新数据fetchData()// 恢复滚动位置restoreScrollPosition()})// 组件被缓存、进入非活跃状态时执行onDeactivated(()=>{// 保存当前状态saveCurrentState()})</script>

4.5 标签页缓存方案

实现类似浏览器的多标签页缓存,需要结合KeepAliveRouterView和标签页管理:

<template><divclass="tabs-layout"><!-- 标签页栏 --><divclass="tabs-bar"><divv-for="tab in tabs":key="tab.path"class="tab-item":class="{ active: tab.path === currentPath }"@click="switchTab(tab)">{{ tab.title }}<spanclass="close-btn"@click.stop="closeTab(tab)">×</span></div></div><!-- 路由视图 + 缓存 --><RouterViewv-slot="{ Component, route }"><KeepAlive:include="cachedTabNames"><component:is="Component":key="route.path"/></KeepAlive></RouterView></div></template>

五、综合实践:完整权限路由方案

将上述四部分整合,形成一个完整的权限路由管理方案:

// stores/permission.jsimport{defineStore}from'pinia'exportconstusePermissionStore=defineStore('permission',{state:()=>({routes:[],permissions:[]}),actions:{generateRoutes(role){// 根据角色获取动态路由constasyncRoutes=getAsyncRoutesByRole(role)// 存储到 storethis.routes=asyncRoutesreturnasyncRoutes}}})// router/index.jsimport{createRouter,createWebHistory}from'vue-router'import{usePermissionStore}from'@/stores/permission'constconstantRoutes=[{path:'/login',name:'Login',component:()=>import('@/views/Login.vue')},{path:'/403',name:'403',component:()=>import('@/views/403.vue')}]constrouter=createRouter({history:createWebHistory(),routes:constantRoutes})// 全局前置守卫router.beforeEach(async(to,from,next)=>{consttoken=localStorage.getItem('token')constpermissionStore=usePermissionStore()if(token){if(to.path==='/login'){next('/')return}// 检查是否已加载权限路由if(!permissionStore.routes.length){try{constuserInfo=awaitgetUserInfo()constroutes=permissionStore.generateRoutes(userInfo.role)routes.forEach(route=>router.addRoute(route))router.addRoute({path:'/:pathMatch(.*)*',redirect:'/404'})// 确保动态路由加载完成后再跳转returnnext({...to,replace:true})}catch(error){// 权限获取失败,跳转登录localStorage.removeItem('token')next(`/login?redirect=${to.fullPath}`)}}else{next()}}else{if(to.meta.requiresAuth!==false){next(`/login?redirect=${to.fullPath}`)}else{next()}}})exportdefaultrouter

路由管理是 Vue 应用架构的核心环节之一。合理的权限控制保证系统安全,动态路由提供灵活的扩展能力,懒加载优化性能,缓存提升用户体验。在实际项目中,这四者往往需要协同配合,共同构建一个健壮、高效的路由系统。