uni-app路由管理神器:vue-router风格体验

为 uni-app 提供类似 vue-router 风格的路由管理系统

前言

在 uni-app 开发中,路由管理一直是一个痛点。原生 API(uni.navigateTouni.redirectTouni.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' })
		}
	}
]

守卫执行顺序:beforeEachbeforeEnterbeforeResolve → 导航 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-plugingenerateRouter 插件,路由名称从 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) // 元信息

声明式导航,模板中直接使用:

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

相关推荐
HjhIron1 小时前
从栈到队列,再到链表:前端开发者必知的线性数据结构
前端·javascript
用户1733598075371 小时前
花两周用 Vue 3 做了个 PDF 工具站,我在生产环境踩了 8 个坑
前端·vue.js
风骏时光牛马1 小时前
TypeScript 泛型与工具类型实战:企业级通用数据请求封装完整案例
前端
阿猫的故乡1 小时前
Vue自定义指令从入门到实用:自动聚焦、权限控制、防抖、懒加载……全案例教学
前端·javascript·vue.js
嘟嘟07171 小时前
吃透 JS 八大数据类型与内存原理,从代码到底层一站式复习
前端
问心无愧05131 小时前
ctf show web入门157 158
前端·笔记
该用户已成仙1 小时前
vue3 使用 vuedraggable 报错 TypeError: isFunction2 is not a function
前端·javascript·vue.js
aidou13141 小时前
Kotlin中实现星级评价选择功能(仅支持整数)
前端·kotlin·自定义view·imageview·ontouchevent·customratingbar
良逍Ai出海1 小时前
我用 Codex 搭了一个 WordPress 独立站
前端