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)
})

_syncedtrue 表示该路由变化由 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',导致守卫链无法区分"前进导航"和"返回导航"。

修复handleGuardResultexecuteNavigation 方法签名新增 '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

相关推荐
qq_2518364572 小时前
基于java Web网络订餐系统设计与实现 源码文档
java·开发语言·前端
飞天狗1112 小时前
零基础JavaWeb入门——第2课:让网页“活”起来 —— JSP是什么?
java·开发语言·前端·后端·web
回忆2012初秋3 小时前
【Nginx】优雅地走进高性能 Web 服务器世界(1)
服务器·前端·nginx
kyriewen3 小时前
Claude Code Token 烧太快?实测 5 招,把月费从 250 美金砍到 50 美金
前端·ai编程·claude
weixin_394758034 小时前
CRMEB Pro 商品字段二开:为什么加一个字段会牵动 SKU、缓存和前端展示?
前端·缓存
IT_陈寒4 小时前
Python的pickle让我半夜加班,这破玩意儿太坑了
前端·人工智能·后端
qq_422152574 小时前
图片格式转换工具怎么选?JPEG、PNG、WebP、AVIF 格式对比与在线转换方案实测
前端
xiaofeichaichai4 小时前
ES 新特性九年速览:从 ES2016 到 ES2024
前端·javascript·es6