Router 路由
@vital-design/router 是面向 uni-app 的路由库:以 pages.json 为唯一数据源,按「包名(pack)+ 路径(path)」跳转,提供 Vue Router 风格的 API。
优势
- 异步导航守卫与响应式路由信息:
beforeEach/afterEach支持async与 Promise;useRoute/usePageRoute返回响应式对象,可直接在模板与计算属性中使用。 - 页面
setup内即可获取完整页面配置:meta/style/config等来自pages.json的字段在进入页面时即可读取,无需另行查表。 - 非首屏页面
setup内无需等待onLoad即可拿到完整路由信息(含query):经router.push/replace等跳转进入时,目标页在beforeCreate/setup阶段即可读到完整query。 - 类型提示与互绑校验:跳转时支持完整路径(如
/pagesBasic/button)与pack+path/name的字面量提示;分包与页面互绑校验,静态跳转与守卫重定向共享同一套位置类型。 pages.json唯一数据源:Vite 插件自动读取并注入,应用侧无需手工维护路由表;跳转以pack+path(或name/ 完整 url)定位,与分包结构对齐。- 跳转 API 均为 Promise:失败(页面不存在、参数非法等)时
reject,可用try/catch或.catch统一处理。 - 全局路由与页面快照分离:
useRoute()跟踪当前活跃页;usePageRoute()固定本页信息,跳转后不被新页覆盖,适合页面内逻辑与布局子组件。
安装与接入
TIP
组件包解压安装时,将 @vital-design/router 一并放入项目 node_modules/@vital-design/ 下(同 快速上手 的引入方式)。
1. 注册 Vite 插件
// vite.config.ts
import { VitePluginRouterPacks } from '@vital-design/router/vite';
export default defineConfig({
plugins: [
UniPages({ /* ... */ }),
// 需注册在会自动生成 pages.json 的插件(如 @uni-helper/vite-plugin-uni-pages 等)之后,
// 以便读取到最新的 pages.json
VitePluginRouterPacks(),
],
});注册后插件自动完成两件事:读取 src/pages.json 注入给路由包(应用侧无需传参);在项目根目录生成 router-packs.d.ts,让跳转时的包名有字面量提示。推荐把 router-packs.d.ts 加入 .gitignore 与 tsconfig include,dev 下 pages.json 变化会自动重新生成。
2. 创建路由实例
// src/router/index.ts
import { createRouter } from '@vital-design/router';
export const router = createRouter();// main.ts
import { router } from './router';
app.use(router); // 注入 $router / $route,并自动同步当前页路由3. 在页面中使用
<script setup lang="ts">
// 可经 unplugin-auto-import 自动导入 useRouter / useRoute / usePageRoute
const router = useRouter();
const route = useRoute(); // 全局当前路由
const pageRoute = usePageRoute(); // 本页路由快照(跳转后不变)
router.push({ pack: 'basic', path: '/button', query: { id: '1' } });
</script>跳转
| 方法 | 说明 |
|---|---|
router.push(location) | 保留当前页面跳转 |
router.pushTab(location) | 跳转 tabBar 页(自动剥离参数) |
router.replace(location) | 关闭当前页跳转 |
router.replaceAll(location) | 关闭所有页打开目标页 |
router.back(delta?) | 返回上 N 级页面,默认 1 |
location 支持三种写法:
// ① 完整 url(补全 pages.json 路径,如 `/pagesBasic/button`;带 query 仍合法)
router.push('/pagesBasic/button?id=1');
// ② 按名称(pages.json 的 name);未写 pack 时仅提示主包 name,分包须带 pack(运行时仍可按名定位)
router.push({ name: 'index', query: { id: '1' } });
router.push({ pack: 'basic', name: 'button' });
// ③ 按包名 + 路径(pack 默认 main)
router.push({ path: '/button', pack: 'basic', query: { id: '1' } });均为 Promise,跳转失败(页面不存在、参数非法等)时 reject,可 try/catch 或 .catch 处理。
获取路由信息
useRoute() — 全局当前路由
返回全局响应式单例(等价于 router.route),始终跟踪最新活跃页。适合布局、全局状态等需要感知「当前在哪个页面」的场景。
usePageRoute() — 页面级路由快照
返回本页独立的响应式快照:只在该页进入时写入,跳转后不会被改成新页。布局内子组件会向上查到所属页面的同一快照;不在页面树内时退回全局 route。
经 router.push / replace 等跳转时,目标页在 beforeCreate / setup 即可读到完整 query(来自跳转入参,不必等小程序 onLoad)。冷启动、直达链接、原生返回等非 router 进入时,仍可能在 onLoad 入参到达后补齐。
选择建议
- 需要实时跟踪当前活跃页(对齐 Vue Router) →
useRoute() - 页面内获取自身路由信息(跳转后不变) →
usePageRoute()
路由字段
两个 API 返回的对象包含相同字段:
| 字段 | 说明 |
|---|---|
route.name | 路由名称(pages.json name 或路径推导) |
route.path | 包内路径,如 /button |
route.pack | 所在包名 |
route.fullPath | 完整路径(含参数),统一以 / 开头,如 /pages/index/index?a=1 |
route.query | 当前页参数 |
route.meta / route.style | pages.json 对应条目字段 |
route.config | pages.json 原始条目(含自定义字段) |
导航守卫
以返回值控制导航:
router.beforeEach((to, from) => {
if (!isLogin.value && to.name !== 'login') {
return { name: 'login' }; // 重定向
}
// 无返回值 / return true → 放行
});
router.beforeEach(async (to) => {
try {
await showDialog({
message: '您还未登录,是否前往登录?',
showCancelButton: true,
});
return { name: 'login' }; // 确认
} catch {
return false; // 取消;抛错 / Promise reject 同样取消
}
});
router.afterEach((to, from) => {
// 导航完成后上报、埋点等
});| 返回值 | 行为 |
|---|---|
undefined / true / 无返回 | 放行 |
false | 取消本次跳转 |
| 字符串或位置对象 | 重定向到该位置 |
守卫按注册顺序依次执行。冷启动、直接输入地址打开、页面返回 / 前进同样触发守卫,此时无上一条路由则 from 为 undefined;入口被重定向时按 replace 跳转,页面已展示时返回 false 仅告警(router.back() 的返回可被拦截)。
包名与根目录配置
默认约定:分包根目录去掉主包根目录(pages)前缀即包名,如 pagesBasic → basic、pagesCase → case。不符合约定时,用插件参数纠正:
VitePluginRouterPacks({
main: 'pages', // 主包根目录不是 pages 时改这里
pattern: /^pages([A-Z][a-z]+)/, // 自定义派生,取捕获组 1 作包名
packs: { case: 'pagesCase' }, // 显式指定 包名 → 根目录
})配置后跳转即可写 { pack: 'case', path: '/x' },url 自动拼接为 /pagesCase/x。
类型提示
注册插件后 router-packs.d.ts 自动生成 PackRegistry 与 RouteRegistry(含 fullPath)。跳转 API(push / pushTab / replace / replaceAll / resolve)与守卫重定向共用同一套互绑位置类型;push 等另有入参重载以默认仅提示主包:
- 字符串 url:
RouteUrl用StringSuggest提示全部已登记完整路径(如/pages/index/index、/pagesBasic/button),仍接受任意 string(含 query) - 未写
pack:path/name只提示主包,且二者须对应同一条路由(如{ path: '/index', name: 'case' }会报错) - 写
pack:提示全部分包名,并收窄到该包的 path / name(同样互绑) - 动态字段:
as RouteLocation(或先写成RouteLocationLoose再断言);动态拼接的 url 本身就是宽string,可直接传入
手动传 packs 时可用泛型补充:
const router = createRouter<'basic' | 'case'>({ ... });
useRouter<'basic' | 'case'>();
useRoute<'basic' | 'case'>();
usePageRoute<'basic' | 'case'>();动态 path / pack
kebabCase() 等返回的是宽 string,模板字符串 `/${kebabCase(name)}` 也是 string,对不上登记表里的字面量,类型会报错。这是预期行为:互绑检查只对静态字面量生效。
动态跳转时任选其一即可:
import type { RouteLocation } from '@vital-design/router';
// ① 整对象断言(推荐)
router.push({
path: `/${kebabCase(name)}`,
pack: kebabCase(nav),
} as RouteLocation);
// ② 完整 url
router.push(`/pagesBasic/${kebabCase(name)}`);守卫重定向直接返回位置对象(与 push 同一套互绑类型)。返回值位无法像 push 入参那样「默认仅主包」:未写 pack 时 path 为全部登记路径;先写 pack 再写 path 即可收窄。
router.beforeEach((to) => {
if (!to.name) return { path: '/index' };
return { pack: 'basic', path: '/button' }; // 建议先写 pack
});静态跳转继续写字面量,保留完整类型检查:
router.push({ pack: 'basic', path: '/button' });createRouter 入参(不走 vite 插件时)
import pagesData from '@/pages.json';
const router = createRouter<'basic' | 'case'>({
pagesData,
packs: { case: 'pagesCase' },
main: 'pages',
});| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| pagesData | pages.json 数据 | PagesData | - |
| packs | 包映射表:包名 → 页面根目录 | Partial<Record<P, string>> | {} |
| main | 主包根目录 | string | 'pages' |
配合 CLI 生成类型声明:
npx vital-router-gen src/pages.json -o router-packs.d.ts --main pages --pack case=pagesCaseVitePluginRouterPacks 配置项
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| pagesJson | pages.json 路径(相对项目根目录) | string | 'src/pages.json' |
| dts | PackRegistry 声明文件输出路径 | string | 'router-packs.d.ts' |
| main | 主包根目录 | string | 'pages' |
| pattern | 分包名派生正则 | RegExp | string | - |
| packs | 显式包映射表 | Record<string, string> | {} |
已知边界
pushTab目标页不带 query(平台限制,自动剥参数)。- 跳转的路径未命中 pages.json 记录时按「包根目录 + path +
/index」兜底并告警,请确保路径已在 pages.json 登记。 - 多段子包根目录(如
pagesBasic/button)默认派生包名为basic/button,想要单层包名请配pattern或packs。
