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

相关推荐
小徐_23332 小时前
Wot UI 2.2.0 发布:Button 新增 subtle,VideoPreview 预览体验继续增强
前端·微信小程序·uni-app
天蓝色的鱼鱼5 小时前
关于 CSS 你可能不知道的属性,但关键时刻很有用
前端·css
泯泷6 小时前
第 2 篇:设计第一套字节码:Opcode、Instruction 与 Constant Pool
前端·javascript·安全
妙码生花6 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十五):优化细节、网络请求封装
前端·后端·ai编程
泯泷6 小时前
第 1 篇:从 1 + 2 开始:亲手写出第一台 JSVM
前端·javascript·安全
团团崽_七分甜6 小时前
Spring Boot 核心知识点总结
前端
lichenyang4536 小时前
从一个按钮开始,理解 ASCF 框架到底在做什么
前端
古夕6 小时前
第三方 SSO 接入实践:redirect_uri 编码、回调一致性与跨项目联调
前端·vue.js
朦胧之6 小时前
页面白屏卡住排查方法
前端·javascript