各位老铁,大家好!今天咱们来聊聊 Vue Router 里那些神出鬼没的“导航守卫”。 别看名字挺唬人,其实它们就是你页面跳转前后的“门卫”,负责检查你的身份、权限,或者做一些额外的处理。 搞清楚它们的执行流程,以后调试路由问题就能少掉几根头发!
导航守卫:路由世界的“保安”
导航守卫,英文名叫 Navigation Guards,顾名思义,就是保护你路由安全的“卫士”。它们可以在路由跳转的不同阶段拦截并控制导航行为。
Vue Router 提供了三种全局导航守卫:
beforeEach
: 全局前置守卫,在路由跳转之前执行。beforeResolve
: 全局解析守卫,在所有组件内守卫和异步路由组件被解析之后执行。afterEach
: 全局后置钩子,在路由跳转之后执行。
除了全局守卫,还有路由独享的守卫和组件内的守卫,但今天咱们主要 focus 在全局守卫上,了解它们的大致流程,其他类型的守卫原理类似。
源码漫游:拨开迷雾见真相
为了彻底搞懂导航守卫的执行流程,咱们需要稍微深入 Vue Router 的源码,看看它背后的机制。 这里我们简化流程,只关注核心部分。
1. 路由匹配与导航流程的启动
首先,当用户点击一个链接或者手动修改地址栏时,Vue Router 会进行路由匹配,找到对应的路由记录。 然后,导航流程正式启动。
2. beforeEach
:第一个“门卫”登场
beforeEach
是第一个被调用的全局守卫。 它可以接收三个参数:
to
: 即将要进入的目标路由对象。from
: 当前导航正要离开的路由对象。next
: 一个函数,用于控制导航流程。
next
函数非常关键,它有以下几种用法:
next()
: 允许导航继续进行。next(false)
: 取消当前导航。next(path)
: 跳转到指定路径。next(error)
: 中断当前导航,并抛出一个错误。
源码片段(简化版):
// 假设这是 Vue Router 内部的导航流程
function navigate(to, from) {
// 1. 执行 beforeEach 守卫
let guards = router.beforeEachGuards; // 获取所有 beforeEach 守卫
let queue = [...guards];
function runQueue(index, resolve, reject) {
if (index >= queue.length) {
return resolve(); // 所有 beforeEach 守卫执行完毕
}
let guard = queue[index];
function next(arg) {
if (arg === false) {
reject(new Error('Navigation cancelled from beforeEach guard'));
} else if (typeof arg === 'string' || typeof arg === 'object') {
// 重定向
// ... (重定向逻辑)
} else {
runQueue(index + 1, resolve, reject); // 执行下一个 beforeEach 守卫
}
}
try {
guard(to, from, next); // 执行守卫
} catch (error) {
reject(error); // 捕获错误
}
}
return new Promise((resolve, reject) => {
runQueue(0, resolve, reject);
});
}
// 使用示例
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth) {
// 检查用户是否已登录
if (isLoggedIn()) {
next(); // 已登录,允许通行
} else {
next('/login'); // 未登录,跳转到登录页面
}
} else {
next(); // 无需认证,允许通行
}
});
重点:
beforeEach
守卫是串行执行的,也就是说,只有前一个beforeEach
守卫调用next()
后,才会执行下一个beforeEach
守卫。- 如果任何一个
beforeEach
守卫调用了next(false)
,整个导航流程都会被中断。 - 如果任何一个
beforeEach
守卫抛出了错误,导航流程也会被中断,并抛出错误。
3. 组件内守卫与异步路由组件的解析
在 beforeEach
守卫执行完毕后,Vue Router 会解析目标路由对应的组件,包括:
- 路由组件本身。
- 组件内的守卫 (例如
beforeRouteEnter
,beforeRouteUpdate
,beforeRouteLeave
)。 - 异步路由组件 (使用
import()
懒加载的组件)。
这个过程涉及到组件的加载、实例化,以及组件内守卫的调用。 细节比较复杂,这里先略过,后面有机会再详细讲解。
4. beforeResolve
:确保一切准备就绪
beforeResolve
守卫在所有组件内守卫和异步路由组件被解析之后执行。 它的参数与 beforeEach
相同:to
, from
, next
。
beforeResolve
的作用是:确保在导航被确认之前,所有需要的数据都已经获取完毕,所有需要异步加载的组件都已经加载完毕。 它可以用来做一些最终的权限检查,或者获取一些关键数据。
源码片段(简化版):
function navigate(to, from) {
// ... (beforeEach 守卫)
// 2. 解析组件和组件内守卫
// ...
// 3. 执行 beforeResolve 守卫
let resolveGuards = router.beforeResolveGuards;
let resolveQueue = [...resolveGuards];
function runResolveQueue(index, resolve, reject) {
//...与runQueue类似,执行beforeResolve守卫
}
return new Promise((resolve, reject) => {
runResolveQueue(0, resolve, reject);
});
}
// 使用示例
router.beforeResolve((to, from, next) => {
// 确保所有数据都已加载完毕
if (to.meta.requiresData) {
fetchData().then(() => {
next(); // 数据加载完毕,允许通行
}).catch(error => {
next(error); // 数据加载失败,中断导航
});
} else {
next(); // 无需加载数据,允许通行
}
});
重点:
beforeResolve
守卫的执行时机是在所有组件内守卫和异步路由组件解析之后。- 它的作用是确保在导航被确认之前,所有需要的数据都已经准备就绪。
- 如果任何一个
beforeResolve
守卫调用了next(error)
,导航流程也会被中断,并抛出错误。
5. 导航被确认
当所有的 beforeEach
和 beforeResolve
守卫都执行完毕,并且没有调用 next(false)
或 next(error)
时,Vue Router 就会确认导航。 这意味着:
- 当前的
from
路由会被卸载。 - 新的
to
路由会被激活。 - 浏览器地址栏会被更新。
6. afterEach
:最后的“送行者”
afterEach
钩子在导航被确认之后执行。 它的参数与 beforeEach
和 beforeResolve
略有不同:
to
: 即将要进入的目标路由对象。from
: 当前导航正要离开的路由对象。
afterEach
不接收 next
函数,因为它无法控制导航流程。 它的作用是:在导航完成后执行一些清理工作,例如:
- 发送统计数据。
- 滚动到页面顶部。
- 设置页面标题。
源码片段(简化版):
function navigate(to, from) {
// ... (beforeEach 和 beforeResolve 守卫)
// 4. 确认导航
// ...
// 5. 执行 afterEach 钩子
router.afterEachGuards.forEach(guard => {
try {
guard(to, from);
} catch (error) {
console.error('Error in afterEach hook:', error);
}
});
}
// 使用示例
router.afterEach((to, from) => {
// 发送统计数据
sendAnalyticsData(to.path);
// 滚动到页面顶部
window.scrollTo(0, 0);
// 设置页面标题
document.title = to.meta.title || 'My App';
});
重点:
afterEach
钩子是在导航被确认之后执行的。- 它不接收
next
函数,无法控制导航流程。 afterEach
钩子是同步执行的。- 即使
afterEach
钩子中抛出了错误,也不会影响导航流程,只会将错误打印到控制台。
执行流程总结
为了更清晰地理解导航守卫的执行流程,咱们用一个表格来总结一下:
阶段 | 守卫/钩子 | 执行时机 | 是否可以控制导航 | 作用 |
---|---|---|---|---|
导航触发 | 用户点击链接或修改地址栏 | 否 | ||
前置守卫 | beforeEach |
路由跳转之前 | 是 | 检查权限、身份验证、重定向等 |
组件解析 | 组件内守卫 | 路由组件和异步路由组件被解析 | 是 | 组件级别的权限控制、数据获取等 |
解析守卫 | beforeResolve |
所有组件内守卫和异步路由组件被解析之后 | 是 | 确保所有数据都已加载完毕,可以进行最终的权限检查等 |
导航确认 | 所有守卫都通过,没有调用 next(false) 或 next(error) |
否 | 卸载 from 路由,激活 to 路由,更新地址栏 |
|
后置钩子 | afterEach |
导航被确认之后 | 否 | 发送统计数据、滚动到页面顶部、设置页面标题等 |
常见问题与注意事项
- 死循环: 如果
beforeEach
守卫中每次都调用next(path)
进行重定向,但path
又指向当前路由,就会导致死循环。 - 异步操作: 在
beforeEach
和beforeResolve
守卫中进行异步操作时,一定要确保在异步操作完成后再调用next()
,否则导航流程可能会提前结束。 - 错误处理: 在
beforeEach
和beforeResolve
守卫中,要妥善处理可能出现的错误,并调用next(error)
中断导航,避免页面出现异常。 - 性能优化:
beforeEach
守卫是串行执行的,所以要尽量避免在其中执行耗时的操作,以免影响页面加载速度。
总结
导航守卫是 Vue Router 中非常重要的一个特性,它可以帮助我们实现各种复杂的路由控制逻辑。 理解导航守卫的执行流程,可以让我们更好地利用它们,构建更安全、更健壮的 Vue 应用。
希望今天的分享对大家有所帮助!下次再见!