7.2 路由进阶:守卫链与按需加载 本节摘要:每次导航要过一条由全局守卫、路由独享守卫、组件内守卫组成的拦截链,然后才解析异步组件并挂载。本节拆解守卫的精确执行顺序,实现一套登录加权限的完整方案(含循环重定向的排查),并用路由懒加载完成页面级分包。 你应当能获得的能力 阅读完本节,你应当能够: 按序写出一次导航触发的全部守卫与钩子; 实现全局登录鉴权与基于角色的路由权限,处理好重定向闭环; 配置路由懒加载并验证分包效果; 用路由元信息承载权限声明,让守卫保持通用。
本节摘要:每次导航要过一条由全局守卫、路由独享守卫、组件内守卫组成的拦截链,然后才解析异步组件并挂载。本节拆解守卫的精确执行顺序,实现一套登录加权限的完整方案(含循环重定向的排查),并用路由懒加载完成页面级分包。
阅读完本节,你应当能够:
一次从 /a 到 /b 的导航,时序如下(Vue Router 4):

守卫三件套的分工记忆法:全局管横切面(登录、埋点),独享管单路由(入口限制),组件内管自身(未保存提醒、参数响应)。守卫里的 next 调用规则:Vue Router 4 也支持返回值风格(return false / return '/login'),一支团队统一一种风格即可,混用是维护灾难。
权限信息放路由元信息 meta 上,守卫保持通用:
const routes = [ { path: '/login', component: Login }, { path: '/finance', component: Finance, meta: { requiresAuth: true, roles: ['admin', 'finance'] } } ]; router.beforeEach(async (to) => { const userStore = useUserStore(); if (to.meta.requiresAuth && !userStore.isLoggedIn) { return { path: '/login', query: { redirect: to.fullPath } }; // 记下来路 } if (to.meta.roles && !to.meta.roles.includes(userStore.role)) { return { path: '/403' }; } return true; // 放行 });
登录成功后跳回记录的来路:
router.push(route.query.redirect || '/');
循环重定向排查:症状是页面在 login 与目标页之间反复横跳,控制台报重定向链。成因几乎总有三类:守卫里 return 的地址本身也命中同一条守卫规则(login 页配了 requiresAuth);登录态判断依据与真实状态不同步(token 存了、store 没刷新);异步恢复登录态时守卫提前放行。第三类的规范解法——在守卫里等恢复完成:
let restored = false; router.beforeEach(async (to) => { if (!restored) { await useUserStore().restoreFromStorage(); // 应用首次导航前恢复会话 restored = true; } // ...后续判断 });
未保存提醒是 beforeRouteLeave 的教科书场景:
onBeforeRouteLeave(() => { if (hasUnsavedChanges.value && !window.confirm('有未保存的修改,确定离开?')) { return false; } return true; });
参数变化响应归 beforeRouteUpdate(或 watch 参数,二选一):/user/1 到 /user/2 组件复用,需要在此重新拉数据。
一个常见反模式要点名:在全局 beforeEach 里调接口校验每个导航的权限。每次换页一次请求,页面切换被网络延迟绑架。正确做法是登录时一次性拉取权限清单存进仓库(或状态对象),守卫只做本地判断,权限变更时显式刷新。
// 每个页面组件改成动态 import,构建工具自动按页面分包 const routes = [ { path: '/', component: () => import('../views/Home.vue') }, { path: '/chart', component: () => import('../views/Chart.vue') }, // 重页面单独成包 { path: '/admin', component: () => import('../views/AdminLayout.vue'), children: [ { path: 'users', component: () => import('../views/admin/Users.vue') } ] } ];
从管线视角看懒加载的收益:首屏只下载与渲染首页所需的代码,图表页那一大包代码在用户点击导航时才由守卫流程中的"解析异步组件"阶段拉取。配合 Vite 的分包策略,可以进一步把公共依赖拆出来避免重复下载。代价是首次进入子页面有网络等待——用路由组件的加载态(defineAsyncComponent 的 loading 参数或全局进度条)盖住白屏。
粒度纪律:路由级懒加载默认全开没问题,但同屏常切换的兄弟路由别为了懒而懒——两三个小页面合成一个包,比每次切换都发请求体验更好。
守卫链的调试技巧值得单独立一条:遇到"导航没反应"或"莫名其妙被重定向",最快的排查方法是在每个守卫的入口打一行日志(打印目标地址与判定结果),刷新页面看日志链在哪一环断掉或拐弯。比盯着配置文件逐行读快得多。另外守卫代码要保持纯粹——判断归守卫、数据获取归 action、副作用归后置钩子,守卫一旦长出几百行的业务逻辑,它就从"关卡"退化成了"迷宫",这时的正确动作是重构而不是继续往里加条件。守卫也应有对应的测试用例:鉴权规则是安全边界,改一行守卫逻辑跑一遍手工回归是多数团队的现状,把典型路径(未登录、无权限、正常进入)写成路由器级别的单元测试,之后每次动守卫都有护栏,成本低收益稳。
页面切换的机制齐了,下一章看数据反向流动的集中场景——表单。