与 vue-router 的差异
unix-router 在 API 风格上对齐 vue-router 4,但由于 uni-app x 采用静态 pages.json 页面模型,二者存在本质差异。本文说明哪些能力等价实现、哪些不适配、哪些行为不同。
能力对照
| vue-router 4 | unix-router(uni-app x) |
|---|---|
createRouter({ history }) | createRouter({ routes }):历史由原生页面栈(getCurrentPages)承担,无 HTML5 history |
| 路径 / 命名匹配 | ✅ 支持(无嵌套段与动态段,见下文) |
route.query | ✅ Map<string, string>,直接进 URL |
route.params | ✅ Map<string, string>,需 ParamsPlugin(__params__ 内部 key 通道),非路径参数 |
route.meta / fullPath | ✅ 支持 |
route.hash | 恒为 ''(无 URL hash 概念) |
currentRoute 响应式 | ✅ 支持 |
| push / replace / back | ✅ 支持(+ relaunch;meta.isTab 自动 switchTab) |
go(n) | ⚠️ 语义受限:用 back(delta) 替代 |
守卫 next() 回调 | ❌ 改为返回值风格:null/true 放行、false 中止、Error 取消、位置对象重定向 |
重复导航 resolve(false) | ❌ 改为 reject NavigationFailure(DUPLICATED,仅 push 检测) |
| 并发导航(后者取消前者) | ❌ 改为自动排队串行执行 |
| beforeEach / beforeResolve / afterEach / beforeEnter | ✅ 支持 |
| onBeforeRouteLeave / Update / Enter | ✅ 支持,但无 keep-alive → Leave 最可靠(见下文) |
useRouter / useRoute / useLink | ✅ 支持(+ useOpenerEventChannel) |
isReady / onError | ✅ 支持(+ onRouteChange) |
| 错误体系 NavigationFailure | ✅ 支持(7 类错误码) |
嵌套路由 children | ❌ 不支持(扁平页面模型) |
动态路由 addRoute / removeRoute | ❌ 不支持(页面必须编译期注册进 pages.json) |
动态路由段 /user/:id | ❌ 不支持(路径即页面路径;用 query / params 传参) |
| 命名视图 / RouterView | ❌ 不支持(无页内渲染占位) |
| scrollBehavior | ❌ 不支持(滚动由 uni-app 原生管理) |
| hash 路由模式 | ❌ 不支持(route.hash 恒为 '') |
app.use(router) 注入 provide / mixin | ⚠️ 仅 H5 端注册($router/$route provide + onShow 全局 mixin 自动同步);原生端均不注册,useRouter 回退全局活跃路由器 |
关键差异说明
1. 扁平页面模型:无嵌套、无动态路由段
页面必须先在 pages.json 注册,path 即真实页面路径(如 pages/detail/detail)。因此:
- 无
children嵌套、无命名视图:页面之间是平级栈关系,"嵌套"语义由页面栈天然承担。 - 无
/user/:id动态段:传参用 query(进 URL)或 params(ParamsPlugin 通道)。 - 无
addRoute / removeRoute:运行时新增的路径无法被编译进包。分包(subPackages)可作资源上的"懒加载",但不是 vue-router 的嵌套/动态语义。
2. 没有 URL / history
uni-app x 无浏览器 URL,"历史"由原生页面栈(getCurrentPages)承担:
- 无
createWebHistory / createWebHashHistory / createMemoryHistory。 route.hash恒为'';fullPath由 path + query 组成。- 无
scrollBehavior,滚动行为由原生管理。 - 返回即出栈:
back(delta)基于页面栈深度,delta 非正整数返回ABORTED,栈深度不足返回CANCELLED。
3. query / params 均为 Map<string, string>
vue-router 的 params 来自路径动态段;unix-router 中 query 与 params 统一为 Map<string, string>(字符串值):
query直接编码进 URL。params经 ParamsPlugin 以内部 key__params__通道跨页传递,不出现在用户可见 query 中,目标页在状态同步时取回重建。- params 需注册 ParamsPlugin(
plugins: [new ParamsPlugin()]);未注册却使用会 rejectPLUGIN_REQUIRED。 - 需要跨刷新保留时可开启
paramsPersistent(持久化到 uni storage,失败回退内存)。
4. 守卫:返回值风格,无 next()
vue-router 守卫通过 next() 回调决定走向;unix-router 守卫直接返回结果:
null/true:放行;false:中止(ABORTED);Error:取消(CANCELLED)。- 返回
string/ 位置对象:重定向;返回{ location, mode? }可指定push/replace/relaunch(缺省沿用原导航模式)。 - 执行链路:beforeEach → beforeEnter(路由独享)→ beforeResolve → 导航 → afterEach(to, from, failure | null)。
- 异步守卫受
guardTimeout(默认 10000ms,0 禁用)保护,超时警告并中止导航。
5. 重复导航 reject,并发导航排队
- 重复导航:vue-router 对重复导航
resolve(false);unix-router 仅 push 检测重复导航并 rejectNavigationFailure(DUPLICATED),需用isNavigationFailure(err, RouterErrorCode.DUPLICATED)捕获,不能依赖"静默成功"。 - 并发导航:vue-router 中后发起的导航会取消前一个;unix-router 将并发导航自动排队串行执行,互不取消。
- 重定向循环防护:守卫重定向深度上限 10(
MAX_REDIRECT_DEPTH),超限返回CANCELLED。 - 结果确认:导航发起后轮询页面栈顶确认是否成功(500ms 内),失败返回
NAVIGATION_API_ERROR。
6. 组件内守卫的局限
uni-app x 页面每次导航创建新实例,无 keep-alive:
onBeforeRouteLeave:最可靠,页面离开(含 back)前触发。onBeforeRouteUpdate:同组件复用场景几乎不存在,极少触发。onBeforeRouteEnter:组件实例尚未创建,无法访问组件状态,效果有限。- 三者均基于 beforeResolve 过滤实现(注册调用本身无返回值、不支持注销,随路由器实例存活)。
7. 命名路由的类型提示分端不同
- WEB 端:
RouteName为keyof RouteNameMap & string,可通过模块增强获得字面量提示:
ts
// 仅对 TS / 编辑器提示有效
declare module '@meng-xi/unix-router' {
interface RouteNameMap {
home: 'home'
about: 'about'
}
}- App / 小程序端:UTS 不支持
keyof组合类型,RouteName退化为string;开启strict后未注册的命名路由在运行时抛ROUTE_NOT_FOUND兜底(strict: false则警告并按路径处理)。
8. app.use(router) 的端差异
- H5 端:
install注册$router/$routeprovide 与全局属性,并注册全局 mixin 在onShow自动syncRoute()。 - 原生端:以上均不注册,仅将路由器设为全局活跃路由器(供
useRouter()回退);建议在页面onShow中自行调用router.syncRoute()。
迁移心智
如果你来自 vue-router,多数代码可直接迁移:createRouter、守卫(改为返回值风格)、useRouter/useRoute/useLink、push/replace/back 用法一致;仅需注意将 components 配置改为 pages.json 注册,放弃动态路由 / 嵌套 / 命名视图 / scrollBehavior,params 改经 ParamsPlugin 传递,重复导航改用 reject 捕获,返回栈深不足会得到 CANCELLED。
