完整导航流程
理解导航的完整解析流程有助于你更好地使用守卫和导航 API。本节将详细说明从导航触发到完成的全过程。
导航触发
导航可以通过以下方式触发:
- 编程式导航:调用
router.push()、router.replace()等方法 - 声明式导航:点击
<RouterLink>组件 - 浏览器操作:点击浏览器的前进/后退按钮
无论通过哪种方式触发,导航都会经过相同的解析流程。
完整流程
以下是导航解析的完整步骤:
1. 外部链接检测
首先检测目标 URL 是否为外部链接。如果是外部链接,直接返回 external 状态,不进入后续流程。
2. 路由匹配
将目标 URL 与路由配置进行匹配。如果没有匹配到任何路由,触发 onNotFound 钩子,并返回 notfound 状态。
3. 重复路由检测
检查目标路由是否与当前路由完全相同。如果相同,返回 duplicated 状态,不执行后续守卫逻辑。
4. 仅哈希变化
如果只有哈希部分发生变化(路径和查询参数不变),执行轻量级更新,跳过守卫流程。
5. 重定向处理
如果路由配置中定义了 redirect,则递归地重新导航到目标路由。递归深度最大为 16 次,超过则抛出错误。
6. 离开守卫
执行当前路由组件的 onBeforeRouteLeave 守卫,按照 由内到外 的顺序(子组件 → 父组件)。如果任何守卫返回 false,导航被中止,返回 aborted 状态。
7. 全局前置守卫
执行所有通过 router.beforeEach() 注册的全局前置守卫,按注册顺序依次执行。如果任何守卫返回 false,导航被中止;如果返回重定向目标,则重新开始导航流程。
8. 路由独享守卫
执行目标路由的 beforeEnter 守卫。对于嵌套路由,按照 父路由优先 的顺序执行。
9. 导航确认
所有守卫通过后,导航被确认。更新路由状态,包括当前路由信息、匹配的组件等。
10. 全局后置钩子
执行所有通过 router.afterEach() 注册的全局后置钩子。此时导航已完成,无法再改变导航结果。
11. 滚动行为处理
根据路由配置的滚动行为策略,处理页面的滚动位置恢复。
流程图
text
导航触发
│
▼
┌─────────────────────┐
│ 1. 外部链接检测 │──── 是 ───→ 返回 external
└─────────────────────┘
│ 否
▼
┌─────────────────────┐
│ 2. 路由匹配 │──── 未匹配 ─→ 触发 onNotFound,返回 notfound
└─────────────────────┘
│ 匹配
▼
┌─────────────────────┐
│ 3. 重复路由检测 │──── 重复 ───→ 返回 duplicated
└─────────────────────┘
│ 不重复
▼
┌─────────────────────┐
│ 4. 仅哈希变化? │──── 是 ───→ 轻量更新,跳过守卫
└─────────────────────┘
│ 否
▼
┌─────────────────────┐
│ 5. 重定向处理 │──── 有重定向 ─→ 递归导航(最多 16 次)
└─────────────────────┘
│ 无重定向
▼
┌─────────────────────┐
│ 6. 离开守卫 │──── 返回 false ─→ 返回 aborted
│ (onBeforeRouteLeave) │ (由内到外)
└─────────────────────┘
│ 通过
▼
┌─────────────────────┐
│ 7. 全局前置守卫 │──── 返回 false ─→ 返回 aborted
│ (beforeEach) │ 返回重定向 ─→ 重新导航
└─────────────────────┘
│ 通过
▼
┌─────────────────────┐
│ 8. 路由独享守卫 │──── 返回 false ─→ 返回 aborted
│ (beforeEnter) │ (父路由优先)
└─────────────────────┘
│ 通过
▼
┌─────────────────────┐
│ 9. 导航确认 │ 更新路由状态
└─────────────────────┘
│
▼
┌─────────────────────┐
│ 10. 全局后置钩子 │ (afterEach)
└─────────────────────┘
│
▼
┌─────────────────────┐
│ 11. 滚动行为处理 │
└─────────────────────┘
│
▼
导航完成完整示例
ts
import { createRouter, defineRoutes, onBeforeRouteLeave } from 'vitarx-router'
const router = createRouter({
routes: [
{ path: '/', component: Home },
{ path: '/login', component: Login },
{
path: '/dashboard',
component: Dashboard,
meta: { requiresAuth: true },
beforeEnter: (to, from) => {
console.log('8. 路由独享守卫 beforeEnter')
}
}
],
scrollBehavior(to, from, savedPosition) {
console.log('11. 滚动行为处理')
if (savedPosition) {
return savedPosition
}
return { top: 0 }
}
})
// 步骤 7:全局前置守卫
router.beforeEach((to, from) => {
console.log('7. 全局前置守卫 beforeEach')
if (to.meta.requiresAuth && !isLoggedIn()) {
return '/login'
}
})
// 步骤 10:全局后置钩子
router.afterEach((to, from) => {
console.log('10. 全局后置钩子 afterEach')
})
// 组件内守卫(步骤 6)
function Dashboard() {
onBeforeRouteLeave((to, from) => {
console.log('6. 离开守卫 onBeforeRouteLeave')
})
}