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() 回调容易忘记调用

typescript 复制代码
// v2.0.x --- 忘记调用 next() 导致导航挂起
router.beforeEach((to, from, next) => {
	const valid = await checkToken()
	if (!valid) {
		// 忘记调用 next(),导航永久挂起
	}
	next()
})

2. 回调嵌套使代码冗长

typescript 复制代码
// 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 无法区分导航成功/失败

typescript 复制代码
// v2.0.x --- afterEach 不知道导航是否成功
router.afterEach((to, from) => {
	// 无法判断导航是否被守卫中止
	// 无法判断 uni API 调用是否失败
})

二、新增能力

1. 守卫返回值模式

v2.1.0 引入 Vue Router 4.x 风格的返回值模式,守卫通过返回值控制导航行为:

typescript 复制代码
// 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. 可控重定向的返回值写法

typescript 复制代码
// 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 参数

typescript 复制代码
// 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 })
	}
})
typescript 复制代码
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 自动识别守卫模式:

vbnet 复制代码
guard.length >= 3 → (to, from, next) → 回调模式(兼容旧版)
guard.length < 3  → (to, from)       → 返回值模式(推荐)
typescript 复制代码
function runGuard(guard, to, from, timeout) {
	const useNextCallback = guard.length >= 3

	if (useNextCallback) {
		return runGuardWithNext(guard, to, from, timeout)
	}

	return runGuardWithReturn(guard, to, from, timeout)
}

返回值模式执行流程

javascript 复制代码
守卫执行
  ├── 返回值 = undefined / true / null → 放行
  ├── 返回值 = false                   → 中止(NAVIGATION_ABORTED)
  ├── 返回值 = RouteLocationRaw        → 重定向
  ├── 返回值 = Error                   → 取消(NAVIGATION_CANCELLED)
  ├── 抛出异常                         → 取消(NAVIGATION_CANCELLED)
  └── 超时                             → 取消(NAVIGATION_CANCELLED)

混用检测

同时使用 next() 回调和返回值的守卫,会在控制台输出警告:

scss 复制代码
Navigation guard "guardName" called next() and also returned a value.
Use either next() callback or return value, not both.

五、完整示例

基础导航守卫

typescript 复制代码
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}`)
})

可控重定向

typescript 复制代码
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' }
	}
})

离开确认

typescript 复制代码
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() 回调模式迁移到返回值模式:

typescript 复制代码
// 迁移前
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 不支持 支持
混用检测警告
相关推荐
猫七先生1 小时前
从零到一:个人博客自动化部署踩坑全记录
前端
淼澄研学1 小时前
Python调用通义千问API实现长尾搜题内容自动化生成实战
前端·react.js·架构
绿岛之北1 小时前
Electron 安全入门:为什么一个 XSS 可能变成 RCE?
前端·electron
舒灿1 小时前
DeepSeek Harness——Agent自我进化的实现途径?
前端·ai编程·deepseek
cindershade2 小时前
React Server Components 在真实项目中的边界:哪些组件该放在服务端
前端
OpenTiny社区2 小时前
GenUI SDK v1.3.0 开发者深度解读:当生成式 UI 开始"长出"工程化骨架
前端·ai编程
前端粉刷匠2 小时前
2025 年是 Agent 的,2026 年是 Harness 的——AI 编程 Harness 架构深度解析
前端·人工智能
张元清2 小时前
React useSessionStorage Hook:刷新不丢、只属于当前标签页的状态 (2026)
前端·javascript·react.js
cindershade2 小时前
为 TypeScript 项目建立可靠的类型边界:API 响应、表单与第三方库
前端