尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

Vue 路由进阶:路由守卫权限控制、动态路由、懒加载、路由缓存 Hi我是前端人类学在 Vue 单页应用开发中路由管理往往从“配置几个页面”演变为“构建一套复杂的访问控制系统”。随着应用规模的增长权限控制、模块动态加载、性能优化和状态保持等问题会接踵而至。本文将从路由守卫权限控制、动态路由、懒加载和路由缓存四个维度深入探讨 Vue Router 的进阶实践。文章目录一、路由守卫构建应用的权限防火墙1.1 全局守卫1.2 路由独享守卫1.3 组件内守卫二、动态路由运行时注册路由2.1 路由初始化与动态添加2.2 路由删除与重置2.3 路由权限的精细化控制三、懒加载路由级代码分割3.1 基本用法3.2 命名 chunk3.3 预加载与预获取3.4 加载状态与错误处理四、路由缓存KeepAlive 与 RouterView4.1 基本缓存4.2 精细控制缓存4.3 基于路由动态决定缓存4.4 缓存下的生命周期4.5 标签页缓存方案五、综合实践完整权限路由方案一、路由守卫构建应用的权限防火墙路由守卫Navigation Guards是 Vue Router 提供的一套钩子函数用于在路由跳转前后执行逻辑。它是实现权限控制的核心机制。1.1 全局守卫全局前置守卫router.beforeEach是最常用的权限控制入口每次路由跳转前都会触发importrouterfrom./routerrouter.beforeEach(async(to,from,next){consttokenlocalStorage.getItem(token)constuserStoreuseUserStore()// 白名单登录页、注册页等无需认证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.titleto.meta.title||默认标题})1.2 路由独享守卫在路由配置中直接定义beforeEnter守卫仅对该路由生效javascriptconst 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 组件内守卫在组件内部使用onBeforeRouteLeave和onBeforeRouteUpdate守卫script setupimport{onBeforeRouteLeave,onBeforeRouteUpdate}fromvue-router// 路由更新时如 /posts/1 - /posts/2onBeforeRouteUpdate((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}fromvue-router// 基础路由登录页、404、白名单页面constconstantRoutes[{path:/login,component:()import(/views/Login.vue)},{path:/404,component:()import(/views/404.vue)}]constroutercreateRouter({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){consttokenlocalStorage.getItem(token)if(token){if(!router.hasRoute(dashboard)){// 获取用户角色constroleawaitgetUserRole()constasyncRoutesasyncRouteMap[role]||[]// 动态添加路由asyncRoutes.forEach(route{router.addRoute(route)})// 添加 404 兜底需放在最后router.addRoute({path:/:pathMatch(.*)*,redirect:/404})// 重新导航确保动态路由生效returnnext({...to,replace:true})}next()}else{// 未登录处理...}})2.2 路由删除与重置在某些场景下如退出登录需要重置路由// 重置路由的方法exportfunctionresetRouter(){// 获取所有动态添加的路由constroutesrouter.getRoutes()routes.forEach(route{// 保留基础路由根据 name 过滤if(route.name!Loginroute.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){constuserStoreuseUserStore()// 检查角色if(to.meta.roles){consthasRoleto.meta.roles.some(roleuserStore.roles.includes(role))if(!hasRole)returnnext(/403)}// 检查权限if(to.meta.permissions){consthasPermissionto.meta.permissions.some(permuserStore.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 :isComponent / /template template #fallback div classloading-spinner加载中.../div /template /Suspense /template /RouterView /template结合 Vue 的Suspense组件可以优雅地处理异步组件的加载状态。四、路由缓存KeepAlive 与 RouterView在多标签页或频繁切换的场景中希望保留页面状态如列表滚动位置、表单输入内容避免重复渲染和接口请求。4.1 基本缓存template RouterView v-slot{ Component } KeepAlive component :isComponent / /KeepAlive /RouterView /template4.2 精细控制缓存通过include和exclude属性控制哪些路由组件需要缓存template RouterView v-slot{ Component } KeepAlive :include[Dashboard, UserList] :max5 component :isComponent / /KeepAlive /RouterView /template4.3 基于路由动态决定缓存利用路由元信息动态决定是否缓存template RouterView v-slot{ Component, route } KeepAlive :includecachedViews component :isComponent :keyroute.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 }) /script4.4 缓存下的生命周期被KeepAlive缓存的组件会经历activated和deactivated生命周期钩子script setupimport{onActivated,onDeactivated}fromvue// 组件从缓存中激活时执行onActivated((){// 刷新数据fetchData()// 恢复滚动位置restoreScrollPosition()})// 组件被缓存、进入非活跃状态时执行onDeactivated((){// 保存当前状态saveCurrentState()})/script4.5 标签页缓存方案实现类似浏览器的多标签页缓存需要结合KeepAlive、RouterView和标签页管理templatedivclasstabs-layout!-- 标签页栏 --divclasstabs-bardivv-fortab in tabs:keytab.pathclasstab-item:class{ active: tab.path currentPath }clickswitchTab(tab){{ tab.title }}spanclassclose-btnclick.stopcloseTab(tab)×/span/div/div!-- 路由视图 缓存 --RouterViewv-slot{ Component, route }KeepAlive:includecachedTabNamescomponent:isComponent:keyroute.path//KeepAlive/RouterView/div/template五、综合实践完整权限路由方案将上述四部分整合形成一个完整的权限路由管理方案// stores/permission.jsimport{defineStore}frompiniaexportconstusePermissionStoredefineStore(permission,{state:()({routes:[],permissions:[]}),actions:{generateRoutes(role){// 根据角色获取动态路由constasyncRoutesgetAsyncRoutesByRole(role)// 存储到 storethis.routesasyncRoutesreturnasyncRoutes}}})// router/index.jsimport{createRouter,createWebHistory}fromvue-routerimport{usePermissionStore}from/stores/permissionconstconstantRoutes[{path:/login,name:Login,component:()import(/views/Login.vue)},{path:/403,name:403,component:()import(/views/403.vue)}]constroutercreateRouter({history:createWebHistory(),routes:constantRoutes})// 全局前置守卫router.beforeEach(async(to,from,next){consttokenlocalStorage.getItem(token)constpermissionStoreusePermissionStore()if(token){if(to.path/login){next(/)return}// 检查是否已加载权限路由if(!permissionStore.routes.length){try{constuserInfoawaitgetUserInfo()constroutespermissionStore.generateRoutes(userInfo.role)routes.forEach(routerouter.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 应用架构的核心环节之一。合理的权限控制保证系统安全动态路由提供灵活的扩展能力懒加载优化性能缓存提升用户体验。在实际项目中这四者往往需要协同配合共同构建一个健壮、高效的路由系统。
返回列表