三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

uni-router v2.1.0 升级:导航守卫全面支持返回值模式

uni-router v2.1.0 升级:导航守卫全面支持返回值模式

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 不支持 支持
混用检测警告
← 返回列表