v0.8.2 为 PATCH 版本(0.8.1 → 0.8.2),新增 RouterOptions.backFallback 配置项,把 0.8.1 引入的「返回栈底兜底」能力扩展到无 tabBar 页的工程。无破坏性变更;未配置 backFallback 时行为与 0.8.1 完全一致,升级无需改动代码。
一、背景:0.8.1 兜底机制与它的目标缺口
v0.8.1 修复了「H5 刷新 / 冷启动直连非首页后返回无效」,核心机制是返回栈底兜底:back() 遇到页面栈不足(栈长 < 2 或 delta >= 栈长)时,自动 relaunch 到入口页而非 reject CANCELLED。
「入口页」的解析方式是自动推断——取路由表中首个声明 meta.isTab: true 的路由。这在有 tabBar 的工程里工作良好,但存在一个缺口:
没有 tabBar 页的工程推断不出兜底目标。 典型形态是 navigationStyle: custom 的自绘导航应用(组件库演示站、全屏 H5 活动页等),页面栈长这样:
- 路由表中没有任何 meta.isTab: true 路由 → 推断结果为 null;
- back() 退回 0.8.1 之前的行为:reject CANCELLED;
- 若调用方(自定义导航栏组件的返回按钮)没有 .catch,H5 控制台出现 Uncaught (in promise)——0.8.1 之前这类工程的问题原样回来,还多了一个报错堆栈。
二、真实案例复盘:unix-ui 组件演示 playground
该工程是典型的无 tabBar 形态:全部页面 navigationStyle: custom,导航由 demo-navbar 组件自绘,返回按钮调用 router.back(1);路由表(router.config.uts)10 条路由均无 isTab;路由器未注册 InterceptorPlugin。
故障链(0.8.1 下复现):
H5 刷新子页 /pages-sub/button/button(页面栈 = 1)
→ 点击 demo-navbar 返回箭头
→ router.back(1)
→ 栈深不足 → backWithStackFallback()
→ findFirstTabRoutePath() = null(无 isTab 路由)
→ reject CANCELLED(NavigationFailure)
→ demo-navbar 未捕获 → Uncaught (in promise) ,页面原地不动
v0.8.2 配置 backFallback: { name: 'home' } 后实测:同场景点击返回 → 正确回首页(pages/index/index),控制台输出警告「按 backFallback 配置兜底重载」,无任何未捕获异常。
三、新增:backFallback 配置项
显式声明兜底目标(RouteLocationRaw,支持命名路由对象或路径字符串),优先级高于 isTab 自动推断:
import { createRouter } from '@meng-xi/unix-router'
export const router = createRouter({
routes,
strict: true,
// 无 tabBar 工程:栈深不足时(H5 刷新后点返回)回首页
backFallback: { name: 'home' }
// 等价写法:backFallback: '/pages/index/index'
})
兜底目标解析优先级:
backFallback 显式配置 → 首个 meta.isTab 路由 → 维持 reject CANCELLED
配套调整:拦截器根路径兜底联动
H5 框架导航栏返回箭头在栈深 = 1 时调用的是 uni.reLaunch({ url: '/' })(uni-h5 内部 onPageHeadBackButton)。InterceptorPlugin 对根路径 reLaunch 的转交逻辑同步调整:
- 路由表有 isTab 路由 → 直接 relaunch 到首个 isTab 路由(与 0.8.1 一致);
- 路由表无 isTab 路由 → 委托 back(1),由 backWithStackFallback 按 backFallback 配置兜底(未配置则维持 CANCELLED)。
各配置组合下的返回行为矩阵
| 有 isTab 路由 + InterceptorPlugin | relaunch 首个 isTab 路由 | isTab 推断兜底 |
| 有 isTab 路由、无拦截器 | 框架原生处理(不经路由器) | isTab 推断兜底 |
| 无 isTab + backFallback + InterceptorPlugin | 委托 back(1) → 兜底到配置页 | 兜底到配置页 |
| 无 isTab + backFallback、无拦截器 | 框架原生处理(不经路由器) | 兜底到配置页 |
| 无 isTab、未配置 backFallback | 框架原生处理 / CANCELLED | reject CANCELLED(未捕获时报错) |
¹ H5 框架导航栏返回箭头仅在页面未使用 navigationStyle: custom 时存在。
四、设计说明:为什么默认不「猜」兜底目标
一个自然的疑问:无 isTab 路由时为什么不自动取「路由表首条路由」兜底,而要求显式配置?
因为首条路由不可靠。以本仓库 playground 为例,routes.gen.uts 由 routeGen 按 localeCompare 排序生成,首条是分包页 pages-sub/checkout/checkout——且声明了 requireAuth: true。若自动兜底到它,会被守卫重定向到登录页,产生比「返回无效」更困惑的行为。入口页在不同工程里可能是首页、引导页或落地页,库无法可靠猜测,显式配置是唯一不产生意外的方式。
这也决定了 backFallback 的定位:有 tabBar 工程零配置不受影响;无 tabBar 工程补一行配置即可获得与 0.8.1 等价的兜底能力。
五、运行验证
| 配置优先级 | 有 tabBar 工程临时配置 backFallback: { name: 'mine' },冷启动详情页后触发 uni.navigateBack | ✅ 落地「我的」页而非首页(配置胜出),警告「按 backFallback 配置兜底重载」正常输出 |
| 无 tabBar 工程实测 | unix-ui playground:冷启动 /pages-sub/button/button → 点自绘导航返回箭头 | ✅ 回首页 pages/index/index,无 Uncaught (in promise) |
| 回归 | unix-router playground(有 tab、未配置)冷启动详情页 → 点返回箭头 | ✅ 行为与 0.8.1 一致(relaunch 首个 isTab 路由) |
以上均为浏览器实测(CDP),验证过程含守卫链日志核对(beforeResolve / afterEach 正常触发)。
六、版本说明
PATCH 版本(0.8.1 → 0.8.2),仅新增一个可选配置项,无 API 移除、无行为变更、无破坏性变更。
版本兼容性
| 核心 API(push / replace / relaunch / back / 守卫 / 插件) | ✅ | ✅(不变) |
| back() 栈深不足(路由表有 isTab 路由、未配置 backFallback) | ✅ 兜底入口页 | ✅(不变) |
| back() 栈深不足(路由表无 isTab 路由、未配置 backFallback) | ❌ reject CANCELLED | ❌ reject CANCELLED(不变) |
| back() 栈深不足(配置 backFallback) | —(无此选项) | ✅ 兜底 relaunch 到配置页 |
七、升级指南
0.8.1 → 0.8.2 无需改动代码:
pnpm add @meng-xi/unix-router@0.8.2
建议:无 tabBar 页的工程(自绘导航 / 全屏应用)补一行 backFallback 配置,使 H5 刷新、返回键、deeplink 冷启动等栈底场景的返回行为可控;有 tabBar 工程无需任何调整。
八、相关链接
- 更新日志
- 路由导航文档(back 行为细节)
- uni API 拦截文档(根路径兜底)
- RouterOptions API
- FAQ:H5 刷新后返回无效
欢迎反馈与共建:GitHub
