路由守卫是框架内用于拦截和控制页面导航的回调机制,能在跳转前后执行鉴权、预取数据、阻止或重定向等操作。本文以 HelloWorld 为例,由浅入深讲清什么是守卫、何时触发、如何实现同步/异步逻辑、跨框架的常见模式与调试技巧,帮助你把守卫用好而不是滥用。
HelloWorld 路由守卫:先把问题讲清楚(用费曼法)

想象一次出门:门外有保安(路由守卫),你要拿出证件(身份信息)给他看,他决定你能不能出去或者去哪个地方。路由守卫就是那位“保安”:把控应用中“从 A 到 B”的每一次导航,允许做检查(鉴权)、准备工作(数据预取)、或直接阻止/改道(重定向)。
为什么需要路由守卫?
- 鉴权与权限控制:未登录或无权限用户不能访问受保护页面。
- 数据预取:在渲染页面前拉取必要数据,避免空白或闪烁。
- 资源与性能控制:按需加载、取消不必要的网络请求或动画。
- 导航一致性:拦截重复提交、确认离开含未保存表单的页面等。
守卫的分类和触发时机(通用视角)
不同框架对守卫的命名和细分不同,但总体思路相近。下面的表格把常见类型列出来,方便快速对照。
| 类型 | 触发时机 | 常见用途 |
|---|---|---|
| 全局守卫 | 任何路由跳转时(所有路由都经过) | 统一鉴权、登录态检查、全局日志埋点 |
| 路由级守卫(per-route) | 仅在匹配到该路由时触发 | 特定路由的权限细分、路由参数校验 |
| 组件内守卫(in-component) | 在组件创建/更新/销毁时运行 | 页面离开确认、依赖组件内状态的跳转判断 |
| 后置钩子 | 导航完成后(常用于记录、清理) | 统计、清理 loading、日志 |
以 Vue(Vue Router)为主的 HelloWorld 实战
接下来我会把思路拆得很清楚:先给出最简单的示例,再逐渐加入鉴权、异步数据预取、和防抖/取消逻辑。代码尽量容易读懂,你可以直接复制到项目里试验。
最简单的 HelloWorld 路由守卫(全局鉴权)
目标:访问 /dashboard 需要登录,未登录跳转到 /login。
// router/index.js (Vue 3 + Vue Router 4)
import { createRouter, createWebHistory } from 'vue-router'
import HelloWorld from '@/views/HelloWorld.vue'
import Login from '@/views/Login.vue'
import Dashboard from '@/views/Dashboard.vue'
import { getAuth } from '@/auth' // 伪函数:返回当前登录信息或 null
const routes = [
{ path: '/', component: HelloWorld },
{ path: '/login', component: Login },
{ path: '/dashboard', component: Dashboard, meta: { requiresAuth: true } }
]
const router = createRouter({
history: createWebHistory(),
routes
})
// 全局前置守卫
router.beforeEach((to, from, next) => {
const user = getAuth()
if (to.meta.requiresAuth && !user) {
next({ path: '/login', query: { redirect: to.fullPath } })
} else {
next()
}
})
export default router
说明:这里使用 meta 字段表示受保护路由,beforeEach 在每次跳转前判断并决定是否继续。这是最常见的用法。
进阶:异步鉴权与返回导航结果(async/await)
很多实际项目的鉴权需要异步向后端或本地缓存验证(例如刷新 token)。保持守卫的可读性和避免回调地狱是关键。
// 使用 async/await:返回一个值来决定
router.beforeEach(async (to, from) => {
if (!to.meta.requiresAuth) return true
try {
const user = await fetchCurrentUser() // 可能触发请求
if (!user) return { path: '/login', query: { redirect: to.fullPath } }
return true
} catch (err) {
return { path: '/login' }
}
})
要点:把守卫写成返回值的 async 函数更直观:返回 true 继续,返回路径对象做重定向,返回 false 阻止导航。
组件内守卫:确认离开(beforeRouteLeave)
典型场景:编辑页面有未保存内容,离开前弹窗确认。
// EditForm.vue
注意:组件内守卫可以直接访问组件实例状态,这是路由级守卫难以做到的。
React Router 的常见实现模式(对照 Vue)
React Router 不像 Vue 有内置的“组件内守卫”,但其思想可以通过组件封装来实现相同效果。常见做法是写一个高阶/包装组件来做鉴权。
简单的 RequireAuth 组件(React Router v6)
// RequireAuth.jsx
import { Navigate, useLocation } from 'react-router-dom'
import { useAuth } from './auth' // 自定义 hook
export default function RequireAuth({ children }) {
const auth = useAuth()
const location = useLocation()
if (!auth.user) {
return
}
return children
}
// 使用
// } />
说明:Use Navigate 做重定向,state 里可以保存跳转来源以便登录后跳回。
在 React 中处理“离开确认”
React 中可以使用浏览器的 beforeunload 或者 react-router 提供的 unstable_useBlocker(早期版本)来实现离开确认。但通常需要在组件中监听表单变化并用 window.beforeunload 做浏览器刷新/关闭的拦截。
常见坑与调试技巧(非常实用)
- 重复导航错误:某些路由库在尝试导航到当前地址时会报错或抛出警告。处理手段:在跳转前 compare to.fullPath 与 from.fullPath,或捕获异常并忽略重复导航。
- 异步竞态(race condition):两个快速连续的导航可能使先发请求返回后才触发后续页面渲染,导致数据错位。解决:在守卫或组件中引入请求取消(AbortController)或在组件层面按 navigation id 判定是否仍然有效。
- 死循环重定向:重定向逻辑不严谨会造成 A -> B -> A 的循环。防护:记录 redirect 来源、限制重定向次数、或在条件中明确排除来自登录页的重定向。
- SSR/同构注意:服务端渲染时路由守卫不能直接访问浏览器 API(如 localStorage 或 window)。把这些操作限定在客户端生命周期或用 cookie/session 在服务端进行鉴权。
- 性能:全局守卫里放入大量同步 expensive 运算会阻塞导航;把耗时逻辑放到 afterEach 或组件层异步加载。
调试技巧清单
- 在守卫内加日志:打印 to、from、用户状态,定位是哪一步返回了重定向或 false。
- 使用网络面板观察守卫触发时的请求顺序,确认预取请求是否被取消。
- 将鉴权逻辑抽离成独立函数,单元测试其不同分支(未登录、token 过期、权限不足)。
- 复现问题时刻意放慢网络(网络面板 throttle),观察异步守卫的表现。
设计守卫时的最佳实践与原则(费曼式总结)
这里把设计守卫时的思路化成几条可执行的原则,像在教朋友一样:简单、清晰、可测、可取消。
- 职责单一:每个守卫只做一件事:鉴权守卫只做鉴权,数据预取守卫只做预取,避免把很多逻辑堆在一个 beforeEach。
- 优先使用同步快速判断:能基于本地状态(比如已登录标志)决定的优先处理,减少不必要的网络请求。
- 异步操作支持取消:对那些可能长时间运行的任务,确保可取消或在返回时判定是否仍然有效。
- 保护而非阻塞用户体验:比如鉴权失败用友好页或模态解释原因,而不是只给空白或错误。
- 可测试与可观察:把守卫拆成可复用函数,写单元测试并在关键点埋日志。
高级话题:多路由守卫协作、预取策略与缓存
当应用复杂时,会出现多个守卫协作的情况。例如:全局鉴权 -> 路由级角色校验 -> 组件数据预取。在这种场景下,明确执行顺序、失败回滚策略和错误上报非常关键。
数据预取策略(懒加载 vs 先行加载)
- 导航前预取(守卫里拉取):保证进入页面时数据已准备好,适合关键数据但会延长导航时间。
- 进入后悬挂加载(组件内 fetch):更快的导航,页面可以显示骨架屏,但需要处理加载状态。
- 混合策略:守卫里只预取必需数据,其他次要数据在组件内异步加载。
缓存与重复请求控制
在守卫或请求层增加缓存策略(内存缓存、stale-while-revalidate)能减少重复请求;当多个导航触发相同请求时,合并请求或复用当前进行的 promise 是常见优化。
常见问题 Q&A(像朋友问你,你边想边答的方式)
Q:守卫里能调用 store 或 router 吗?
A:可以,但有顺序依赖要注意。全局守卫注册通常在 router 创建后、app 使用前或 app 创建时完成。要确保 store 已准备好,或用延迟初始化的方式避免引用未初始化的对象。
Q:守卫里抛出异常怎么办?
A:最好捕获并处理异常,返回一个安全路由或显示错误页。未捕获的异常可能阻止后续导航并造成页面卡死。
Q:前端守卫能完全替代后端鉴权吗?
A:不能。前端守卫只是 UX 层的保护,真正的安全必须由后端控制数据访问并验证权限。
收尾随想(写着写着想到的一些小建议)
写路由守卫的时候,总会有人把所有逻辑塞到 beforeEach 里——工作虽快,但以后维护会哭。我自己也犯过,把太多逻辑放全局,后来 refactor 花了不少时间。实践中,先把“能在组件处理的放回组件”,把“必须在跳转前处理的放守卫”,把“全局公用的抽成工具”。另外,给守卫写点测试会省很多调试时间,尤其是在权限矩阵复杂的企业级应用中。
参考资料(可进一步阅读的书目与文档名)
- Vue Router 官方文档(导航守卫章节)
- React Router 文档(Authentication & Protected Routes)
- “前端工程化与路由设计” 相关文章与社区讨论
好了,到这里你已经有了从原理到实战、从简单示例到进阶注意点的一套完整思路。接下来可以把示例代码粘进你的 HelloWorld 项目里,按真实需求一步步替换伪造函数,边跑边改,遇到具体问题再查日志或用上文的调试清单逐一排查。