为 uni-app 提供类似 vue-router 风格的路由管理系统
前言
在 uni-app 开发中,路由管理一直是一个痛点。原生 API(uni.navigateTo、uni.redirectTo、uni.navigateBack)虽然能满足基本导航需求,但缺少路由守卫、命名路由、元信息等 vue-router 开发者习以为常的能力。每次需要登录拦截、页面标题设置、权限控制时,都不得不在各个页面中重复编写逻辑。
@meng-xi/uni-router 的目标很简单:让 uni-app 也能拥有 vue-router 级别的路由管理体验,同时不破坏 uni-app 原有的页面模型。
核心特性
vue-router 风格 API
如果你用过 vue-router,上手成本为零:
typescript
const router = useRouter()
// 路径导航
await router.push('/pages/about/index')
// 带查询参数
await router.push({ path: '/pages/about/index', query: { id: '1' } })
// 命名路由导航
await router.push({ name: 'about' })
// 替换当前页面
await router.replace('/pages/about/index')
// 返回
await router.back()
await router.back(2) // 返回两级
底层自动映射到 uni-app 原生 API:
| uni-router 方法 | uni-app API |
|---|---|
push() |
uni.navigateTo / uni.switchTab |
replace() |
uni.redirectTo / uni.switchTab |
back() |
uni.navigateBack |
路由守卫
这是 uni-router 最核心的能力。四层守卫机制,完整覆盖导航生命周期:
typescript
// 全局前置守卫 - 最常用的登录拦截
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth && !isLoggedIn()) {
next({ path: '/pages/login/index', query: { redirect: to.fullPath } })
} else {
next()
}
})
// 全局解析守卫 - 所有前置守卫通过后执行
router.beforeResolve((to, from, next) => {
next()
})
// 全局后置钩子 - 导航完成后执行,无法改变导航结果
router.afterEach((to, from) => {
console.log(`导航完成: ${from.path} → ${to.path}`)
})
// 路由独享守卫 - 在路由配置中定义
const routes = [
{
path: '/pages/admin/index',
name: 'admin',
beforeEnter: (to, from, next) => {
if (isAdmin()) next()
else next({ path: '/pages/403/index' })
}
}
]
守卫执行顺序:beforeEach → beforeEnter → beforeResolve → 导航 API → afterEach
守卫支持三种行为:
next()- 放行next({ path: '/login' })- 重定向next(false)- 中止导航
命名路由
告别硬编码路径字符串,通过 name 进行导航:
typescript
// 路由配置
const routes = [{ path: '/pages/about/index', name: 'about', meta: { title: '关于' } }]
// 导航时使用名称
await router.push({ name: 'about' })
await router.push({ name: 'about', query: { tab: 'info' } })
配合 @meng-xi/vite-plugin 的 generateRouter 插件,路由名称从 pages.json 自动生成,无需手动维护。
路由元信息
在 meta 中附加自定义数据,配合守卫实现权限控制、标题设置等:
typescript
const routes = [
{ path: '/pages/index/index', name: 'home', meta: { title: '首页' } },
{ path: '/pages/user/index', name: 'user', meta: { requireAuth: true } },
{ path: '/pages/settings/index', name: 'settings', meta: { title: '设置', requireAuth: true } }
]
// 守卫中读取 meta
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth && !isLoggedIn()) {
next({ name: 'login' })
} else {
// 设置页面标题
if (to.meta.title) {
uni.setNavigationBarTitle({ title: to.meta.title })
}
next()
}
})
TypeScript 类型提示
启用 dts: true 后,路由名称和路径获得自动补全和类型检查:
typescript
// 路由名称自动补全
router.push({ name: 'pagesAboutIndex' }) // ✅ 自动补全
router.push({ name: 'invalidName' }) // ❌ 类型错误
// 路径自动补全
router.push({ path: '/pages/about/index' }) // ✅ 自动补全
router.push({ path: '/invalid/path' }) // ❌ 类型错误
错误处理
完整的错误体系,支持全局捕获和局部 try/catch:
typescript
// 全局错误处理
router.onError((error, to, from) => {
switch (error.code) {
case 'NAVIGATION_DUPLICATED':
console.warn('重复导航', error.message)
break
case 'NAVIGATION_ABORTED':
console.warn('导航被中止')
break
case 'NAVIGATION_API_ERROR':
console.error('导航 API 调用失败', error.cause)
break
}
})
// 局部错误处理
try {
await router.push('/pages/protected/index')
} catch (e) {
if (e.code === 'NAVIGATION_ABORTED') {
// 导航被守卫中止
}
}
| 错误码 | 说明 |
|---|---|
NAVIGATION_ABORTED |
导航被守卫中止 |
NAVIGATION_CANCELLED |
导航被取消(重定向超限) |
NAVIGATION_DUPLICATED |
重复导航到当前位置 |
ROUTE_NOT_FOUND |
未找到匹配的路由 |
NAVIGATION_API_ERROR |
uni 导航 API 调用失败 |
SETUP_ERROR |
路由器初始化或使用方式错误 |
组合式 API
在 Vue 3 组合式 API 中便捷访问路由器:
typescript
import { useRouter, useRoute } from '@meng-xi/uni-router'
// 在 setup 中使用
const router = useRouter()
const route = useRoute()
console.log(route.path) // 当前路径
console.log(route.query) // 查询参数
console.log(route.meta) // 元信息
RouterLink 组件
声明式导航,模板中直接使用:
vue
<template>
<RouterLink to="/pages/about/index">关于页面</RouterLink>
<RouterLink :to="{ name: 'about' }" replace>替换导航</RouterLink>
<RouterLink :to="{ path: '/pages/about/index', query: { id: '1' } }"> 带参数导航 </RouterLink>
</template>
<script setup>
import { RouterLink } from '@meng-xi/uni-router/components/RouterLink.vue'
</script>
uni API 拦截
可选功能,启用后直接调用 uni.navigateTo 等原生 API 也会走路由守卫流程:
typescript
const router = createRouter({
routes,
strict: true,
interceptUniApi: true // 启用拦截
})
启用后,以下调用都会自动经过守卫链:
typescript
// 这两种方式等价,都会触发 beforeEach / afterEach
await router.push('/pages/about/index')
uni.navigateTo({ url: '/pages/about/index' })
路由状态同步
当页面通过浏览器后退、物理返回键等非路由器方式切换时,调用 syncRoute() 确保路由状态与实际页面一致:
typescript
// 在页面 onShow 中调用
onShow() {
router.syncRoute()
}
快速开始
1. 安装
bash
pnpm add @meng-xi/uni-router
pnpm add @meng-xi/vite-plugin -D
2. 配置 Vite 插件
typescript
// vite.config.ts
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
import { generateRouter } from '@meng-xi/vite-plugin'
export default defineConfig({
plugins: [
uni(),
generateRouter({
pagesJsonPath: 'src/pages.json',
outputPath: 'src/router.config.ts',
dts: true,
metaMapping: {
navigationBarTitleText: 'title',
requireAuth: 'requireAuth'
}
})
]
})
3. 创建路由器
typescript
// src/main.ts
import { createSSRApp } from 'vue'
import { createRouter } from '@meng-xi/uni-router'
import routes from './router.config'
import App from './App.vue'
const router = createRouter({
routes,
strict: true
})
// 注册守卫
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth && !isLoggedIn()) {
next({ path: '/pages/login/index', query: { redirect: to.fullPath } })
} else {
next()
}
})
export function createApp() {
const app = createSSRApp(App)
app.use(router)
return { app }
}
4. 在组件中使用
vue
<script setup>
import { useRouter, useRoute } from '@meng-xi/uni-router'
const router = useRouter()
const route = useRoute()
async function goToAbout() {
await router.push({ name: 'about' })
}
</script>
与 pages.json 的关系
Uni Router 不替代 pages.json,而是与之配合:
| 职责 | pages.json | Uni Router |
|---|---|---|
| 页面注册 | 必须声明 | 不负责 |
| 路由导航 | uni.navigateTo 等 | push / replace / back |
| 路由守卫 | 不支持 | beforeEach 等 |
| 路由元信息 | 不支持 | meta 字段 |
| 命名路由 | 不支持 | name 字段 |
API 概览
Router 实例方法
| 方法 | 说明 |
|---|---|
router.push(location) |
导航到新页面 |
router.replace(location) |
替换当前页面 |
router.back(delta?) |
返回上一页或多级页面 |
router.syncRoute() |
同步路由状态与实际页面栈 |
router.beforeEach(guard) |
注册全局前置守卫 |
router.beforeResolve(guard) |
注册全局解析守卫 |
router.afterEach(guard) |
注册全局后置钩子 |
router.onError(handler) |
注册错误处理回调 |
router.resolve(location) |
解析路由位置(不导航) |
router.getRoutes() |
获取所有路由配置 |
router.hasRoute(name) |
检查路由是否存在 |
写在最后
v1.0.0 是 @meng-xi/uni-router 的首个正式版本,涵盖了 uni-app 路由管理中最核心的需求:导航、守卫、元信息、类型提示。如果你在 uni-app 项目中需要比原生 API 更强大的路由能力,欢迎试用。
反馈和贡献请前往 GitHub Issues。