Vue Router 导航守卫实战:路由复用、离开确认与异步竞态
本文目录
旧题库里的守卫代码使用了 next 回调,这通常对应 Vue Router 3 或旧式写法。读代码先看依赖版本:Vue Router 3 的守卫流程要求通过 next()、next(false) 或 next(location) 完成导航;当前 Vue Router 推荐在守卫中返回 undefined/true、false 或重定向位置。两个版本的概念相近,API 不能不标版本地混抄。
守卫分别负责什么
| 位置 | 常见用途 | 注意事项 |
|---|---|---|
全局 beforeEach | 登录态、粗粒度导航策略 | 重定向必须排除登录页等目标,避免循环 |
路由 beforeEnter | 某个路由首次进入时的条件 | 同一路由记录内只改 params/query/hash 时不会重新触发 |
组件 beforeRouteUpdate / onBeforeRouteUpdate | 组件复用时响应路由参数变化 | 不能依赖 mounted 再执行一次 |
组件 beforeRouteLeave / onBeforeRouteLeave | 保护未保存表单、释放页面导航前资源 | 只保护 SPA 内导航,不等同于可靠的浏览器关闭拦截 |
beforeResolve | 页面组件和其他守卫都允许后、确认前的检查 | 放置确实必须在进入前完成的许可或关键数据准备 |
afterEach | 成功导航后的标题、埋点、菜单收起 | 不能取消导航;区分导航失败再上报成功事件 |
守卫不是每个组件请求都需要加的一道“保险”。把鉴权重定向、页面离开确认和数据加载分开,才容易理解失败时谁负责恢复。
Vue Router 4+ 返回式写法
新写法优先让返回值表达结果。异步守卫可以等待权限信息,但必须避免每条导航都重复请求;鉴权失败到登录页时保留安全的站内返回地址,并排除登录路由以免重定向循环。
router.beforeEach(async (to) => {
if (to.meta.requiresAuth && !(await sessionStore.ensureLoaded())) {
return {
name: 'login',
query: { redirect: to.fullPath },
};
}
if (to.meta.requiresAuth && !sessionStore.isAuthenticated) {
return { name: 'login', query: { redirect: to.fullPath } };
}
});
return false 中止当前导航;返回路由位置会启动重定向;不返回值则允许继续。Vue Router 4 仍兼容第三个 next 参数,但它已是容易被重复调用的兼容形式,升级后的代码优先统一改成返回式,别在同一个守卫里同时 return 和调用 next。
Vue Router 3 的旧守卫需要 next 完成流程,且每条逻辑路径必须且只能调用一次:
const guards = {
beforeRouteUpdate(to, from, next) {
this.loadUser(to.params.id)
.then(() => next())
.catch((error) => next(error));
},
};
如果守卫里发请求,要为取消、错误、重复导航和组件销毁定义行为;不要在 Promise 成功与失败分支之后再无条件调用第二次 next()。大多数数据加载可以在路由变化后由页面数据层处理,只有必须阻止页面进入的场景才放进阻塞式守卫。
动态参数变化会复用组件
从 /users/1 跳到 /users/2 时,如果匹配同一个路由组件,Vue Router 会复用组件实例。生命周期不会重新走一遍 mounted;要监听具体参数,或者使用路由更新守卫。不要整体深度 watch 整个 route 对象,只观察页面真正依赖的 param/query。
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate, useRoute } from 'vue-router'
import { ref, watch } from 'vue'
const route = useRoute()
const user = ref(null)
const loading = ref(false)
const errorMessage = ref('')
watch(() => route.params.id, async (id, _oldId, onCleanup) => {
const controller = new AbortController()
onCleanup(() => controller.abort())
loading.value = true
errorMessage.value = ''
try {
const response = await fetch('/api/users/' + encodeURIComponent(String(id)), {
signal: controller.signal,
credentials: 'same-origin',
})
if (!response.ok) throw new Error('HTTP ' + response.status)
user.value = await response.json()
} catch (error) {
if (error.name !== 'AbortError') errorMessage.value = '用户资料加载失败'
} finally {
if (!controller.signal.aborted) loading.value = false
}
}, { immediate: true })
onBeforeRouteUpdate((to) => {
if (to.params.id === route.params.id) return
// 参数变化允许继续;数据 watcher 会加载新用户。
})
onBeforeRouteLeave(() => {
if (hasUnsavedChanges()) return window.confirm('离开并放弃未保存的修改吗?')
})
</script>
示例突出边界而非完整组件:具体错误类型要校验,返回 JSON 需要 schema 验证;watcher 取消旧请求能降低竞态,但服务端仍需处理可能已经到达的请求。若使用守卫预取,导航等待数据期间应呈现明确的 pending 状态,并允许后续导航取消旧任务。
beforeRouteEnter 为什么没有 this
进入目标页面前,组件实例尚未创建,所以 beforeRouteEnter 不能访问组件的 this。旧式 Vue Router 3 常用 next(vm => vm.load()) 在实例创建后回调;当前代码通常把初始加载放到 setup/watch 或清晰的数据加载层。不要在全局守卫中直接操作组件实例。
Composition API 的 onBeforeRouteUpdate / onBeforeRouteLeave 可在 router-view 后代组件中注册,并在组件卸载时自动移除。useRoute() 是响应式对象,依赖时选具体字段;如页面只需要 /users/:id 的用户详情,就监听 route.params.id,而不是所有 query/hash 变化都重新请求。
离开确认与浏览器关闭
离开守卫适合防止 SPA 内导航丢失草稿。它应依据真正的 dirty 状态,保存成功后立即清除 dirty 标记,并覆盖菜单、面包屑、返回按钮、程序化 router.push 等入口。浏览器关闭/刷新另有 beforeunload 限制,通常只在确有未保存数据时短暂注册;提示文案由浏览器控制、移动端支持不一致,不能把它当作自动保存或数据持久化。
更可靠的方式是输入后定时保存草稿、导航前 flush、失败保留本地草稿,并清楚展示“已保存/保存中/保存失败”。危险操作仍需服务端版本冲突检测,不能只在客户端拦截离开。
导航结果、失败和测试
router.push() 是异步导航。需要等路由确认后再关闭移动菜单或更新页面标题;检查返回的 Navigation Failure,区分被守卫中止、被新导航取消和重复导航。重定向通常开启一次新导航,不等同于失败。异常交给 router.onError 或页面级错误呈现,别把网络失败静默当作权限拒绝。
回归至少覆盖:
- 登录页本身不会被全局登录守卫重定向成循环。
- 从列表进入详情、同组件切换参数、返回和刷新都加载正确数据。
- 连续快速切换参数时旧请求取消或被序号屏蔽,迟到结果不覆盖新路由。
- 401、403、404、服务端 500、慢响应和取消分别显示正确状态。
- 未保存表单的 SPA 离开、保存成功后离开、浏览器返回和关闭提示符合预期。
- 守卫重定向、重复点击、导航中再次点击和异步组件失败都无未处理 Promise。
- 直接访问 API 仍由后端认证/授权;路由守卫只改善导航体验,不是安全边界。
参考:Vue Router Navigation Guards、Vue Router Composition API、Vue Router Navigation Failures、Vue Router 3 Navigation Guards。
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。