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() 触发,不是一次完整的导航(未经过前置守卫)。
RouterLink 错误事件
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。