uni-router v1.1.1发布:守卫超时保护+路由监听

v1.1.0 新增守卫超时保护、路由变化监听等能力;v1.1.1 修复 back() 守卫执行、状态同步和兼容性问题

v1.1.0:从"能用"到"好用"

v1.0.0 建立了 uni-router 的核心能力------导航、守卫、元信息、类型提示。但在实际项目中,几个高频问题反复出现:

  • 守卫中写了异步请求,忘了调 next(),导航永远挂起
  • 想监听路由变化做埋点,只能在每个 afterEach 里手动处理
  • 物理返回键导致路由状态不同步,onRouteChange 无法感知
  • RouterLink 导航失败时没有反馈,用户无感知

v1.1.0 针对这些场景逐一解决。

守卫超时保护:防止导航永久挂起

这是 v1.1.0 最重要的新增能力。在实际项目中,守卫函数经常包含异步逻辑:

typescript 复制代码
router.beforeEach(async (to, from, next) => {
	// 网络请求可能超时、可能失败
	const hasPermission = await checkPermission(to.meta)
	if (hasPermission) next()
	else next({ name: 'login' })
})

如果 checkPermission 永远不返回(网络挂起、逻辑遗漏),next() 永远不会被调用,导航就永远挂起------页面既不跳转也不报错,用户看到的是"点了没反应"。

v1.1.0 新增 guardTimeout 配置项:

typescript 复制代码
const router = createRouter({
	routes,
	guardTimeout: 10000 // 10 秒超时,默认值
})

当守卫在指定时间内既未调用 next() 也未返回 rejected Promise 时,框架自动中止导航并输出警告:

复制代码
[uni-router] Navigation guard timed out after 10000ms. Navigation aborted.

设为 0 可禁用超时保护(不推荐)。

实现原理 :框架为每个守卫创建超时计时器,与守卫的 Promise 使用 Promise.race 竞争:

typescript 复制代码
private runGuardWithTimeout(guard, to, from): Promise<GuardResult> {
	const guardPromise = runGuard(guard, to, from)
	if (!this._guardTimeout) return guardPromise

	const timeoutPromise = new Promise<GuardResult>(resolve => {
		setTimeout(() => {
			warn(`Navigation guard timed out after ${this._guardTimeout}ms. Navigation aborted.`)
			resolve({ type: 'abort', code: NAVIGATION_CANCELLED })
		}, this._guardTimeout)
	})

	return Promise.race([guardPromise, timeoutPromise])
}

超时后守卫仍可能继续执行并调用 next(),但此时导航已被中止,next() 调用会被静默忽略(通过 resolved 标志位保证 next() 只生效一次)。

路由变化监听:onRouteChange

v1.0.0 提供了 afterEach 后置钩子,但它只在路由器主动发起的导航完成时触发。当路由状态通过 syncRoute() 同步时(如物理返回键),afterEach 不会触发。

v1.1.0 新增 onRouteChange(),统一监听所有路由变化:

typescript 复制代码
router.onRouteChange((to, from) => {
	// 导航完成和状态同步都会触发
	analytics.track('page_view', { from: from.path, to: to.path })
})

与 afterEach 的区别:

特性 afterEach onRouteChange
路由器导航完成 触发 触发
syncRoute 同步 不触发 触发
可移除 返回移除函数 返回移除函数
用途 导航后置逻辑 路由状态变化订阅

设计考量 :afterEach 是导航流程的一部分,语义上"导航完成后执行";onRouteChange 是状态订阅,语义上"路由状态变了就通知我"。两者职责不同,不应合并。

路由状态同步标记:RouteLocation._synced

onRouteChange 统一了两种路由变化的监听,但有时需要区分变化来源------比如埋点时,导航触发的页面浏览需要记录,但状态同步触发的可能是重复记录。

v1.1.0 在 RouteLocation 上新增 _synced 标记:

typescript 复制代码
router.onRouteChange((to, from) => {
	if (!to._synced) {
		// 真正的导航,记录页面浏览
		analytics.track('page_view', { path: to.path })
	}
	// 状态同步(如物理返回键),仅更新 UI 状态
	updateActiveTab(to.path)
})

_synced 为 true 表示该路由变化由 syncRoute() / syncCurrentRoute() 触发,不是一次完整的导航(未经过前置守卫)。

v1.0.0 的 RouterLink 组件在导航失败时静默吞掉错误,开发者无法在模板层面处理失败场景。v1.1.0 新增 @error 事件:

vue 复制代码
<mxuni-router to="/pages/protected/index" @error="onNavError">
	<view>需要登录的页面</view>
</mxuni-router>
typescript 复制代码
function onNavError(error: NavigationFailure) {
	if (error.code === 'NAVIGATION_ABORTED') {
		uni.showToast({ title: '导航被拦截', icon: 'none' })
	}
}

其他优化

  • fullPath 确定性 --- buildFullPath 对 query 参数键排序,确保 { a: '1', b: '2' } 和 { b: '2', a: '1' } 生成一致的 fullPath,避免因参数顺序不同导致重复导航误判
  • install 类型修正 --- install(app) 参数类型从 unknown 改为 App,IDE 智能提示更准确
  • uni API 拦截增强 --- 拦截器逻辑优化,提升拦截稳定性
  • 守卫执行增强 --- 守卫链执行逻辑优化,支持超时保护与异常处理

v1.1.1:关键 Bug 修复

v1.1.1 修复了 v1.1.0 中发现的 4 个问题,其中 2 个影响核心功能。

back() 未触发 afterEach

问题 :router.back() 导航完成后,afterEach 后置钩子未执行。如果依赖 afterEach 做页面标题设置或埋点,返回操作会"漏掉"。

原因 :back() 方法在调用 goBack() 后只执行了 syncCurrentRoute(),遗漏了 runAfterGuards()。

修复:

typescript 复制代码
async back(delta = 1) {
	// ...守卫执行...
	try {
		await goBack(delta)
		this.syncCurrentRoute(from)
		this.guardManager.runAfterGuards(to, from) // 补充触发 afterEach
	} catch (error) {
		// ...
	}
}

back() 守卫模式错误

问题 :back() 导航的守卫模式使用了 'push',导致守卫链无法区分"前进导航"和"返回导航"。

修复 :handleGuardResult 和 executeNavigation 方法签名新增 'back' 模式,back() 调用时传入 'back'。

syncRoute() 忽略 query 变化

问题 :syncRoute() 只比较路径,不比较查询参数。当页面 query 变化但路径不变时(如 /pages/detail?id=1 → /pages/detail?id=2),路由状态不会更新。

修复:

typescript 复制代码
syncRoute(): void {
	const from = this.routeState.getCurrentRoute()
	const currentPath = getCurrentPagePath()
	const currentQuery = getCurrentPageQuery()
	// 同时比较路径和查询参数
	if (currentPath === from.path && this.isSameQuery(currentQuery, from.query)) return
	this.syncCurrentRoute(from)
}

app.onUnmount 兼容性

问题 :install 中直接调用 app.onUnmount(),但这是 Vue 3.5+ 新增 API,uni-app 的 app 实例不支持,导致运行时报错 app.onUnmount is not a function。

修复:添加防御性检查:

typescript 复制代码
if (typeof app.onUnmount === 'function') {
	app.onUnmount(() => removeInterceptors())
}

在 uni-app 中应用不会真正卸载,拦截器无需清理,因此跳过此调用不影响功能。

升级指南

从 v1.1.0 升级到 v1.1.1 无需任何代码修改,直接更新依赖即可:

bash 复制代码
pnpm add @meng-xi/uni-router@1.1.1

uni_modules 用户将 mxuni-router 目录替换为新版本即可。

版本对比

能力 v1.0.0 v1.1.0 v1.1.1
路由导航 ✅ ✅ ✅
路由守卫 ✅ ✅ ✅
命名路由 ✅ ✅ ✅
路由元信息 ✅ ✅ ✅
TypeScript 类型提示 ✅ ✅ ✅
uni API 拦截 ✅ ✅(增强) ✅
守卫超时保护 ❌ ✅ ✅
路由变化监听 ❌ ✅ ✅
RouterLink @error ❌ ✅ ✅
back() afterEach ❌ ❌(缺失) ✅(修复)
syncRoute() query ❌ ❌(缺失) ✅(修复)
app.onUnmount 兼容 ❌ ❌(报错) ✅(修复)

写在最后

v1.1.x 系列的核心主题是健壮性 ------守卫超时保护防止导航挂起,onRouteChange 弥补状态同步的监听缺口,Bug 修复确保核心流程正确执行。如果你已经在使用 v1.0.0 或 v1.1.0,强烈建议升级到 v1.1.1。

反馈和贡献请前往 GitHub Issues。

相关推荐
溪语流沙7 小时前
【Web全栈进阶】PostgreSQL上手:Docker跑库 + 把早报站从SQLite迁过去
前端·docker·postgresql
IT_陈寒7 小时前
SpringBoot自动配置的坑:你以为的捷径可能是弯路
前端·人工智能·后端
paopaokaka_luck7 小时前
高校社团管理(AI辅助任务分配、协同过滤算法推荐、ECharts数据可视化、活动参与闭环、校园交流与反馈、器材借还管理)
java·前端·javascript·spring boot·数据分析·echarts
Dovis(誓平步青云)8 小时前
导览音频切换太快,旧讲解不能覆盖新展品
android·前端·javascript·ecmascript·音视频·宠物
福兮说19 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
郑州光合科技余经理19 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
小呆呆66620 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
Dovis(誓平步青云)20 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan21 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_21 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线