v2.1.0 将导航守卫全面升级为返回值模式(与 Vue Router 4.x 一致),通过
return undefined/return false/return RouteLocationRaw控制导航行为,无需调用next()回调。旧版next()回调模式保持兼容,标记为已弃用。
前言
@meng-xi/uni-router 的守卫系统自 v1.0 起一直使用 next() 回调模式控制导航行为。Vue Router 4.x 已全面移除 next() 回调,改为返回值模式,代码更简洁、更符合 async/await 风格。
v2.1.0 将守卫升级为返回值模式,同时保持对旧版 next() 回调的完全兼容,提供平滑迁移路径。
一、问题分析
1. next() 回调容易忘记调用
// v2.0.x — 忘记调用 next() 导致导航挂起
router.beforeEach((to, from, next) => {const valid = await checkToken()if (!valid) {// 忘记调用 next(),导航永久挂起}next()
})
2. 回调嵌套使代码冗长
// v2.0.x — 回调嵌套,可读性差
router.beforeEach((to, from, next) => {if (to.meta.requireAuth) {checkAuth(result => {if (result) {next()} else {next({ name: 'login' })}})} else {next()}
})
3. afterEach 无法区分导航成功/失败
// v2.0.x — afterEach 不知道导航是否成功
router.afterEach((to, from) => {// 无法判断导航是否被守卫中止// 无法判断 uni API 调用是否失败
})
二、新增能力
1. 守卫返回值模式
v2.1.0 引入 Vue Router 4.x 风格的返回值模式,守卫通过返回值控制导航行为:
// v2.1.0 — 返回值模式
router.beforeEach((to, from) => {if (to.meta.requireAuth && !isLoggedIn()) {return { name: 'login' } // 重定向}// 不返回值或 return true 表示放行
})// 异步守卫
router.beforeEach(async (to, from) => {const valid = await checkToken()if (!valid) return false // 中止
})
返回值对照表
| 返回值 | 行为 |
|---|---|
undefined / void / true |
放行,继续执行下一个守卫 |
false |
中止导航(NAVIGATION_ABORTED) |
string(如 '/login') |
重定向到路径 |
RouteLocationRaw(如 { name: 'login' }) |
重定向到路由位置 |
Error 对象 |
取消导航(NAVIGATION_CANCELLED) |
| 抛出异常 | 取消导航(NAVIGATION_CANCELLED) |
2. 可控重定向的返回值写法
// v2.1.0 — 通过返回值中的 mode 字段指定重定向方式
router.beforeEach((to, from) => {if (to.meta.requireAuth && !isLoggedIn()) {return { location: { name: 'login' }, mode: 'replace' }}if (to.meta.roles && !hasRole(to.meta.roles)) {return { location: { name: 'home' }, mode: 'relaunch' }}
})
3. afterEach 接收 failure 参数
// v2.1.0 — afterEach 可区分导航成功/失败
router.afterEach((to, from, failure) => {if (failure) {console.error('导航失败:', failure.message)return}// 导航成功,设置页面标题if (to.meta.title) {uni.setNavigationBarTitle({ title: to.meta.title as string })}
})
4. NavigationGuardReturn 类型
type NavigationGuardReturn = void | undefined | boolean | RouteLocationRaw | Error | null
三、Bug 修复
1. next() 未调用导致导航挂起
修复前:next() 回调模式中,忘记调用 next() 会导致导航永久挂起,需要超时机制兜底,但超时后中止导航而非放行。
修复后:返回值模式中,不返回值等同于 return undefined,自动放行。旧版回调模式保持超时保护。
2. 守卫中止后 afterEach 缺少失败信息
修复前:守卫中止导航时,afterEach 无法获取 NavigationFailure 信息。
修复后:守卫中止、uni API 调用失败等场景,afterEach 的第三个参数 failure 会传入对应的 NavigationFailure 实例。
四、架构设计
守卫模式自动检测
通过函数参数个数 guard.length 自动识别守卫模式:
guard.length >= 3 → (to, from, next) → 回调模式(兼容旧版)
guard.length < 3 → (to, from) → 返回值模式(推荐)
function runGuard(guard, to, from, timeout) {const useNextCallback = guard.length >= 3if (useNextCallback) {return runGuardWithNext(guard, to, from, timeout)}return runGuardWithReturn(guard, to, from, timeout)
}
返回值模式执行流程
守卫执行├── 返回值 = undefined / true / null → 放行├── 返回值 = false → 中止(NAVIGATION_ABORTED)├── 返回值 = RouteLocationRaw → 重定向├── 返回值 = Error → 取消(NAVIGATION_CANCELLED)├── 抛出异常 → 取消(NAVIGATION_CANCELLED)└── 超时 → 取消(NAVIGATION_CANCELLED)
混用检测
同时使用 next() 回调和返回值的守卫,会在控制台输出警告:
Navigation guard "guardName" called next() and also returned a value.
Use either next() callback or return value, not both.
五、完整示例
基础导航守卫
import { createRouter } from '@meng-xi/uni-router'const router = createRouter({routes: [{ path: 'pages/index/index', name: 'home' },{ path: 'pages/login/login', name: 'login' },{ path: 'pages/protected/protected', name: 'protected', meta: { requireAuth: true } }]
})// 返回值模式(推荐)
router.beforeEach((to, from) => {if (to.meta.requireAuth && !isLoggedIn()) {return { name: 'login' }}
})// 异步守卫
router.beforeEach(async (to, from) => {const user = await fetchUser()if (to.meta.roles && !user.roles.includes(to.meta.roles)) {return { name: '403' }}
})// 后置钩子(接收 failure 参数)
router.afterEach((to, from, failure) => {if (failure) {console.error('导航失败:', failure.message)return}console.log(`导航成功: ${from.path} → ${to.path}`)
})
可控重定向
router.beforeEach((to, from) => {if (to.name === 'protected' && !isLoggedIn()) {// replace 模式:登录后不保留受保护页面的历史return { location: { name: 'login' }, mode: 'replace' }}if (to.meta.roles && !hasRole(to.meta.roles)) {// relaunch 模式:清空栈回到首页return { location: { name: 'home' }, mode: 'relaunch' }}
})
离开确认
router.beforeEach((to, from) => {if (from.meta.dirty) {return new Promise(resolve => {uni.showModal({title: '提示',content: '有未保存的修改,确认离开?',success: res => {resolve(res.confirm ? true : false)}})})}
})
六、升级指南
v2.1.0 完全向后兼容,无需修改现有代码即可升级。
推荐迁移
推荐逐步将守卫从 next() 回调模式迁移到返回值模式:
// 迁移前
router.beforeEach((to, from, next) => {if (condition) {next({ name: 'login' })} else {next()}
})// 迁移后
router.beforeEach((to, from) => {if (condition) {return { name: 'login' }}
})
新旧对照表
| 场景 | 旧版 next() 回调 |
新版返回值 |
|---|---|---|
| 放行 | next() |
return undefined 或不写 |
| 放行(显式) | next() |
return true |
| 中止 | next(false) |
return false |
| 重定向 | next({ name: 'login' }) |
return { name: 'login' } |
| 重定向+方式 | next({ name: 'login' }, { mode: 'replace' }) |
return { location: { name: 'login' }, mode: 'replace' } |
| 抛出错误 | next(new Error('msg')) |
throw new Error('msg') |
| 返回错误 | — | return new Error('msg') |
不需要改动
- 使用
next()回调的旧守卫代码无需修改,保持完全兼容 - 守卫注册 API(
router.beforeEach/beforeResolve/afterEach/beforeEnter)签名不变 - 守卫移除函数(返回值)不受影响
- 超时配置(
guardTimeout)不受影响
版本兼容性
| 功能 | v2.0.x | v2.1.0 |
|---|---|---|
next() 回调模式 |
支持 | 支持(已弃用) |
| 返回值模式 | 不支持 | 支持 |
afterEach 接收 failure |
不支持 | 支持 |
| 混用检测警告 | 无 | 有 |