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 >= 3

	if (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.xv2.1.0
next() 回调模式支持支持(已弃用)
返回值模式不支持支持
afterEach 接收 failure不支持支持
混用检测警告
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦曦i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值