编程 beforeEnter 在 /users/2 → /users/3 时不触发:导航守卫 12 步顺序与三个边界

2026-11-03 00:03:56

导航守卫 12 步解析顺序与三个触发边界

站内另一篇讲的是用守卫做鉴权时容易踩到的坑(无限重定向、刷新丢动态路由、redirect 开放跳转),这篇只解决一个问题:导航被触发之后,这些守卫按什么顺序执行,以及哪些情况下根本不会进入守卫。

先记住 12 步,而不是记一堆钩子名

官方文档给出的完整导航解析流程:

  1. 导航被触发
  2. 在失活的组件里调用 beforeRouteLeave
  3. 调用全局 beforeEach
  4. 在重用的组件里调用 beforeRouteUpdate(2.2+)
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局 beforeResolve(2.5+)
  9. 导航被确认
  10. 调用全局 afterEach
  11. 触发 DOM 更新
  12. 执行 beforeRouteEnter 中传给 next 的回调,组件实例作为参数传入

这张表的顺序可以直接推出三个结论:

  • beforeEnter 排在 beforeRouteUpdate 之后。同一个路由记录内参数变化(/users/2 → /users/3)走的是第 4 步,第 5 步根本不执行。
  • 全局守卫的顺序是 beforeEach → beforeResolve → afterEach,越靠后能改的东西越少:afterEach 不接收 next,也改不了导航。
  • beforeRouteEnter 在第 7 步执行,那时组件实例还没创建,this 是 undefined;能拿到实例的只有第 12 步的 next 回调。

边界一:beforeEnter 只管「从别的路由进来」

很多人写:

{
path: '/users/:id',
component: UserDetail,
beforeEnter: (to, from) => {
// 想把「这个 id 我有没有权限看」放这里
if (!canView(to.params.id)) return '/403'
},
}

第一次从列表页点进 /users/2,它执行;用户在详情页里点「下一条」跳到 /users/3,URL 变了、组件复用了,但 beforeEnter 不跑。官方文档写得很直白:beforeEnter 只在进入路由时触发,不会在 params、query 或 hash 改变时触发,只有从一个不同的路由导航时才触发。

要处理复用场景,有两种写法:

// 选项式 API
beforeRouteUpdate(to, from) {
this.load(to.params.id)
}

//  里没有这个选项,用组合式函数
import { onBeforeRouteUpdate } from 'vue-router'
onBeforeRouteUpdate((to) => {
load(to.params.id)
})

或者干脆 watch 参数。还有第三种情况容易漏:/users/2#info → /users/2#projects 只改 hash,同样不进任何进入类守卫。

边界二:数据请求放 beforeResolve,不放 afterEach

beforeResolve 是最后一个全局守卫,位置在「所有组件内守卫和异步路由组件都解析完、导航还没确认」之间。官方对它用途的描述就是:拉数据,或者执行那些「用户进不去这个页面就不该做」的操作。

对比一下三个位置:

位置能拦导航异步组件是否已加载适合做什么
beforeEach能否登录态、路由级权限判断
beforeResolve能是依赖已加载组件的取数、权限二次确认
afterEach不能是埋点、改标题、滚动复位

放 afterEach 里请求也能跑,但那时导航已确认、DOM 已更新,请求失败只能自己在页面里兜——用户看到的是先闪出一个空页面再报错。要「进不去就别请求」,就得往前放。

边界三:返回值只有三种语义,与 next 等价

Vue Router 4 里守卫可以返回值,也可以继续用第三个参数 next,两者等价:

返回值next 写法结果
falsenext(false)取消导航;若浏览器 URL 已变(手输地址或点后退),URL 重置回 from
一个路由地址next('/login') 或 next({ name: 'Login' })中断当前导航,以同一个 from 起一个新导航;可带 replace: true
undefined 或 truenext()放行,进入下一个守卫
抛 Errornext(new Error())取消导航并交给 router.onError 注册的回调

next 的经典错误是调用两次:

// BAD
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
next() // 上面已经 next 过一次了
})

// GOOD
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})

官方说明 next 仍然被支持,但已经通过 RFC 决定移除,原因是它是最常见的错误来源。新代码直接返回值,不要混着写——混用 async/await 和 next 时,守卫可能永远不 resolve,页面就停在那里不动。

异步守卫挂起时,URL 和视图本来就可以不一致

守卫是异步解析的,所有守卫 resolve 之前导航一直处于 pending。这期间如果用户手改了地址栏、或者按了后退,浏览器 URL 已经变了,但导航没确认:

  • 最终放行,视图跟上去;
  • 最终 return false,URL 被重置回 from。

所以调试时看到「地址栏是 /orders,页面还是 /users」,先别怀疑 router 坏了,去检查某个守卫里是不是有个没 return 的 await 分支。

快速自检

  1. 权限判断放在 beforeEach,取数据放在 beforeResolve,埋点放在 afterEach。
  2. 路由参数变化要不要刷新,用 onBeforeRouteUpdate 或 watch 参数,不要指望 beforeEnter。
  3. 新代码只用返回值,不用 next。
  4. 有 await 的守卫,每条分支都要 return。

官方文档:https://router.vuejs.org/zh/guide/advanced/navigation-guards.html

复制全文 生成海报 导航守卫 Vue Router 前端路由

推荐文章

程序员茄子在线接单