本文基于 v1.4.0 版本,全面介绍 @meng-xi/uni-router 的设计理念与完整功能。
为什么需要 uni-router?
uni-app 原生路由系统基于 pages.json 静态配置,导航通过 uni.navigateTo / uni.redirectTo / uni.reLaunch / uni.navigateBack 等 API 直接调用,缺少以下关键能力:
- 无路由守卫
:无法在导航前进行权限校验、登录检查等拦截 - 无命名路由
:必须硬编码路径字符串,重构时容易遗漏 - 无路由元信息
:无法为路由附加标题、权限标记等结构化数据 - 无错误体系
:导航失败时只能通过回调获取,缺乏统一的错误处理机制 - 无状态同步
:浏览器后退、物理返回键等场景下路由状态可能不一致 - 无页面间通信
:无法在导航时建立页面间的事件通道
@meng-xi/uni-router 的目标是在 uni-app 的静态页面模型上,提供一套 vue-router 风格的路由管理方案,让开发者在 uni-app 中也能享受现代化的路由开发体验。
核心设计理念
不替代 pages.json,而是与之配合
uni-router 不替代pages.json。页面注册仍由 pages.json 负责,uni-router 在此基础上提供路由导航、守卫、元信息等增强能力。这种设计确保了:
完全兼容 uni-app 的页面管理机制 不影响 pages.json的条件编译等原生能力可以渐进式引入,无需改造现有项目
基于 uni-app 原生 API 实现
所有导航操作最终通过 uni.navigateTo / uni.redirectTo / uni.switchTab / uni.reLaunch / uni.navigateBack 执行,不绕过 uni-app 的页面管理机制,确保跨平台行为一致。
功能全景
一、路由导航
push — 导航到新页面
// 路径字符串await router.push('/pages/about/about')// 路径对象 + 查询参数await router.push({ path: '/pages/about/about', query: { id: '1' } })// 命名路由await router.push({ name: 'about' })// 带动画参数(仅 App 端生效)await router.push({ path: '/pages/about/about', animation: { type: 'slide-in-bottom', duration: 500 } })// 带页面间通信const result = await router.push({path: '/pages/about/about',events: {// 监听目标页面通过 eventChannel.emit 发送的事件receiveData: (data) => { console.log('收到数据:', data) }}})// 通过 eventChannel 向目标页面发送事件result.eventChannel?.emit('fromOpener', { msg: '你好' })
push 自动根据 meta.isTab 选择 uni.navigateTo(普通页面)或 uni.switchTab(TabBar 页面),开发者无需手动判断。
push 返回 NavigationResult,包含目标路由位置和可选的 eventChannel,用于页面间双向通信。
replace — 替换当前页面
await router.replace('/pages/login/login')await router.replace({ name: 'home' })
对应 uni.redirectTo。替换 TabBar 页面时自动切换为 uni.switchTab(会关闭所有非 Tab 页面)。
relaunch — 关闭所有页面并打开目标页面
// 路径字符串await router.relaunch('/pages/index/index')// 命名路由 + 查询参数await router.relaunch({ name: 'login', query: { redirect: '/about' } })
对应 uni.reLaunch,常用于:
退出登录后跳转登录页 从深层页面返回首页 重置整个页面栈
设计细节:
TabBar 页面自动切换为 uni.switchTabuni.reLaunch不支持动画参数,传入时输出警告 不进行重复导航检测(清栈场景下目标页面可能就是当前页面) 走完整守卫链(beforeEach → beforeEnter → beforeResolve → afterEach)
back — 返回上一页
await router.back() // 返回上一页await router.back(2) // 返回两级await router.back(1, { type: 'slide-out-left' }) // 指定动画
back() 执行完整的守卫链,守卫可以中止或重定向返回操作。若未指定动画参数,将使用目标页面的 meta.animation 作为默认动画。
重复导航检测
push 到当前页面时自动拒绝并抛出 NAVIGATION_DUPLICATED 错误,避免重复入栈。
注意:
relaunch不进行重复导航检测。因为清栈场景下目标页面可能就是当前页面(如"返回首页"),拒绝此类导航没有意义。
并发导航排队
多次并发导航自动排队,前一次完成后再执行下一次,避免页面栈混乱。
二、页面间通信(EventChannel)
v1.4.0 新增基于 uni.navigateTo 的 EventChannel 机制,实现页面间双向通信。
什么是 EventChannel?
EventChannel 是 uni-app 提供的页面间通信通道,允许发起导航的页面和被打开的页面通过事件机制互相发送数据。这在以下场景中非常有用:
打开详情页时传递初始数据 子页面操作完成后通知父页面刷新 表单页提交结果回传到列表页
编程式导航通信
发起页 — 通过 push 的 events 参数和返回值通信
// 发起页const result = await router.push({path: '/pages/detail/detail',query: { id: '1' },events: {// 监听目标页面 emit 的事件acceptData: (data) => {console.log('收到详情页数据:', data)},// 监听详情页的编辑完成通知editComplete: (data) => {console.log('编辑完成:', data)// 刷新列表等操作}}})// 通过 eventChannel 向目标页面发送事件result.eventChannel?.emit('initData', { msg: '来自发起页的初始数据' })
目标页 — 通过 getOpenerEventChannel 接收和回复
// 目标页(detail)export default {onLoad() {const eventChannel = this.getOpenerEventChannel()// 监听发起页发来的事件eventChannel.on('initData', (data) => {console.log('收到初始数据:', data)})// 向发起页发送数据eventChannel.emit('acceptData', { msg: '详情页已加载' })},methods: {onSave() {const eventChannel = this.getOpenerEventChannel()// 通知发起页编辑完成eventChannel.emit('editComplete', { id: 1, action: 'saved' })}}}
声明式导航通信(RouterLink)
<template><mxuni-router:to="{ path: '/pages/detail/detail', query: { id: '1' } }":events="{ acceptData: (data) => onAcceptData(data) }"@navigated="onNavigated"@error="onNavError"><view>跳转到详情页</view></mxuni-router></template><scriptsetup>function onAcceptData(data) {console.log('收到详情页数据:', data)}function onNavigated(eventChannel) {// 导航成功后,通过 eventChannel 向目标页面发送事件eventChannel?.emit('initData', { msg: '来自 RouterLink 的初始数据' })}function onNavError(error) {console.log('导航失败:', error.code)}</script>
EventChannel 接口
interfaceEventChannel{/** 监听事件,每次 emit 都会触发 */on(event: string, callback: (...args: any[]) => void): EventChannel/** 监听事件,仅触发一次后自动移除 */once(event: string, callback: (...args: any[]) => void): EventChannel/** 取消监听事件 */off(event: string, callback?: (...args: any[]) => void): EventChannel/** 触发事件 */emit(event: string, ...args: any[]): EventChannel}
通信限制
push | uni.navigateTo | NavigationResult.eventChannel | |
replace | uni.redirectTo | events 时输出警告并忽略 | |
relaunch | uni.reLaunch | events 时输出警告并忽略 | |
back | uni.navigateBack | ||
uni.switchTab | events 时输出警告并忽略 |
三、路由守卫
路由守卫是 uni-router 最核心的能力,提供完整的导航拦截机制。
全局前置守卫 — beforeEach
在每次导航前执行,常用于登录验证:
router.beforeEach((to, from, next) => {if(to.meta.requireAuth && !isLoggedIn()) {next({ name: 'login', query: { redirect: to.fullPath } })} else {next()}})
全局解析守卫 — beforeResolve
在所有前置守卫和路由独享守卫完成后执行,适合需要确保所有守卫都已通过的场景:
router.beforeResolve((to, from, next) => {// 所有前置守卫已通过,导航即将执行next()})
全局后置钩子 — afterEach
在导航完成后执行,不影响导航结果,适合埋点、标题设置等操作:
router.afterEach((to, from) => {if (to.meta.title) {uni.setNavigationBarTitle({ title: to.meta.title as string })}})
路由独享守卫 — beforeEnter
在路由配置中定义,仅对该路由生效:
const routes = [{path: 'pages/admin/admin',name: 'admin',meta: { requireAuth: true },beforeEnter: (to, from, next) => {if (isAdmin()) next()else next({ name: 'forbidden' })}}]
支持数组形式,按顺序依次执行:
const routes = [{path: 'pages/admin/admin',name: 'admin',beforeEnter: [checkAuth, checkAdmin]}]
守卫执行顺序
beforeEach → beforeEnter → beforeResolve → 导航执行 → afterEach守卫重定向
守卫中调用 next(location) 可重定向到其他路由,支持多级重定向(最大深度 10):
router.beforeEach((to, from, next) => {if (to.meta.requireAuth && !isLoggedIn()) {next({ name: 'login' }) // 重定向到登录页} else {next()}})
守卫超时保护
通过 guardTimeout 配置项(默认 10000ms),防止守卫未调用 next() 导致导航永久挂起:
const router = createRouter({routes,guardTimeout: 15000 // 15 秒超时})
守卫注册返回移除函数
所有守卫注册函数均返回一个移除函数,支持动态注册和注销:
const removeGuard = router.beforeEach((to, from, next) => {// 临时守卫逻辑next()})// 不再需要时移除removeGuard()
四、命名路由
通过 name 字段进行导航,避免硬编码路径字符串:
// 路由配置const routes = [{ path: 'pages/index/index', name: 'home', meta: { isTab: true } },{ path: 'pages/about/about', name: 'about', meta: { title: '关于' } }]// 导航时使用名称await router.push({ name: 'about', query: { id: '1' } })
配合 @meng-xi/vite-plugin 自动生成的类型声明,路由名称可获得 TypeScript 自动补全和类型检查。
五、路由元信息
meta 字段支持页面标题、权限标记、TabBar 标识、导航动画等自定义数据:
interface RouteMeta {title?: string // 页面标题isTab?: boolean // 是否为 TabBar 页面requireAuth?: boolean // 是否需要登录认证animation?: NavigationAnimation // 默认导航动画(仅 App 端)[key: string]: unknown // 自定义扩展字段}
使用示例:
const routes = [{ path: 'pages/index/index', name: 'home', meta: { isTab: true, title: '首页' } },{ path: 'pages/about/about', name: 'about', meta: { animation: { type: 'fade-in' } } },{ path: 'pages/admin/admin', name: 'admin', meta: { requireAuth: true } }]
六、导航动画
完整的页面切换动画支持,仅 App 端生效,其他平台自动忽略。
NavigationAnimation 接口
interface NavigationAnimation {type: UniAnimationType // 动画类型duration?: number // 持续时间(ms),默认 300}
UniAnimationType — 完整动画类型
slide-in-right | slide-out-right |
slide-in-left | slide-out-left |
slide-in-top | slide-out-top |
slide-in-bottom | slide-out-bottom |
fade-in | fade-out |
zoom-out | zoom-in |
zoom-fade-out | zoom-fade-in |
pop-in | pop-out |
autonone | autonone |
三种使用方式
1. 导航时传入动画参数
await router.push({ path: '/pages/about/about', animation: { type: 'slide-in-bottom' } })await router.back(1, { type: 'slide-out-left', duration: 500 })
2. 路由级默认动画(meta.animation)
const routes = [{ path: 'pages/about/about', name: 'about', meta: { animation: { type: 'fade-in' } } }]3. RouterLink 声明式动画
<RouterLinkto="/pages/about/about":animation="{ type: 'slide-in-bottom' }">底部滑入</RouterLink>
动画优先级
push/replace/back 调用时传入 > meta.animation > uni 默认值各导航方式对动画的支持
push | uni.navigateTo | ||
replace | uni.redirectTo | ||
relaunch | uni.reLaunch | ||
back | uni.navigateBack | ||
uni.switchTab |
传入动画参数但不支持时,路由器会输出 console.warn 提醒开发者。
七、uni API 拦截
通过 interceptUniApi 选项拦截原生导航 API,确保路由守卫始终生效:
const router = createRouter({routes,interceptUniApi: true // 拦截 uni.navigateTo 等原生 API})
启用后,以下调用将被拦截并转由路由器处理:
// 这两种方式等价,都会经过守卫链uni.navigateTo({ url: '/pages/about/about' })router.push('/pages/about/about')
拦截原理
通过 uni.addInterceptor注册拦截器路由器内部发起的 API 调用通过计数器标记放行,避免重复执行守卫 外部调用被拦截后,阻止原始 API 执行,转由 router.push/replace/relaunch/back执行完整守卫链低版本小程序基础库兼容:修改 args.url为空字符串,防止忽略返回值继续执行
拦截范围
uni.navigateTo | router.push |
uni.redirectTo | router.replace |
uni.switchTab | router.push |
uni.reLaunch | router.relaunch |
uni.navigateBack | router.back |
八、组合式 API
useRouter — 获取路由器实例
import { useRouter } from '@meng-xi/uni-router'const router = useRouter()await router.push('/pages/about/about')
必须在 Vue 组件的 setup() 中调用,通过 Vue 的 inject 机制获取路由器实例。
useRoute — 获取响应式路由位置
import { useRoute } from '@meng-xi/uni-router'const route = useRoute()// route 是 Ref<RouteLocation>,路由变化时自动更新console.log(route.value.path)console.log(route.value.query)
同一 router 实例共享同一个响应式 ref,通过 WeakMap 缓存避免重复创建。
Options API — $router 和 $route
在 Options API 组件中,可通过 this.$router 和 this.$route 访问路由器:
export default {methods: {goToAbout() {this.route.path}}}
为避免与 uni-app H5 内置的 vue-router 冲突,
$router和$route仅在未被定义时设置。
九、RouterLink 组件
基于 uni-app navigator 封装的声明式导航组件:
<template><!-- 路径跳转 --><RouterLinkto="/pages/about/about">关于页面</RouterLink><!-- 命名路由 + 替换模式 --><RouterLink:to="{ name: 'about' }"replace>替换导航</RouterLink><!-- relaunch 模式(关闭所有页面并打开目标页面) --><RouterLinkto="/pages/index/index"relaunch>返回首页</RouterLink><!-- 带动画参数 --><RouterLinkto="/pages/about/about":animation="{ type: 'fade-in' }">淡入动画</RouterLink><!-- 带页面间通信 --><RouterLink:to="{ path: '/pages/detail/detail', query: { id: '1' } }":events="{ acceptData: (data) => console.log(data) }"@navigated="onNavigated"@error="onNavError">跳转并通信</RouterLink></template><scriptsetup>import { RouterLink } from '@meng-xi/uni-router/components/RouterLink.vue'function onNavigated(eventChannel) {// 向目标页面发送事件eventChannel?.emit('initData', { msg: '来自 RouterLink' })}function onNavError(error) {console.log('导航失败:', error.code)}</script>
Props
to | RouteLocationRaw | ||
replace | boolean | false | |
relaunch | boolean | false | |
animation | NavigationAnimation | undefined | |
events | EventListeners | undefined | |
hoverClass | string | 'navigator-hover' | |
hoverStopPropagation | boolean | false | |
hoverStartTime | number | 50 | |
hoverStayTime | number | 600 |
Events
navigated | EventChannel | void | |
error | NavigationFailure |
十、路由状态同步
当页面通过浏览器后退、物理返回键等非路由器方式切换时,路由器的 currentRoute 可能与实际页面不同步。syncRoute() 方法从 uni-app 页面栈中读取当前页面信息并更新路由状态:
// 在每个页面的 onShow 生命周期中调用import { onShow } from '@dcloudio/uni-app'import { useRouter } from '@meng-xi/uni-router'const router = useRouter()onShow(() => {router.syncRoute()})
onRouteChange — 路由变化监听
注册路由状态变化监听器,导航完成和状态同步时都会触发:
router.onRouteChange((to, from) => {console.log(`路由变化: {to.path}`)})
与 afterEach 不同,onRouteChange 也会捕获 syncRoute() 触发的状态变化。
十一、错误处理
完整的错误体系
// RouterError — 路由错误基类classRouterErrorextendsError{readonly code: RouterErrorCode}// NavigationFailure — 导航失败,包含来源和目标信息classNavigationFailureextendsRouterError{readonly to: RouteLocationreadonly from: RouteLocationreadonly cause?: unknown}
错误码
NAVIGATION_ABORTED | |
NAVIGATION_CANCELLED | |
NAVIGATION_DUPLICATED | |
ROUTE_NOT_FOUND | |
NAVIGATION_API_ERROR | |
SETUP_ERROR |
全局错误捕获
router.onError((error, to, from) => {if (error.code === 'NAVIGATION_ABORTED') {console.log('导航被中止')}})
onError 返回移除函数,支持动态注销:
const removeHandler = router.onError((error, to, from) => {// 错误处理})removeHandler() // 移除此处理器
十二、TypeScript 类型提示
配合 @meng-xi/vite-plugin 自动生成的类型声明,为路由导航提供类型安全:
// 路由名称自动补全router.push({ name: 'pagesIndexIndex' }) // ✅ 自动补全router.push({ name: 'invalidName' }) // ❌ 类型错误// 路径自动补全router.push({ path: '/pages/index/index' }) // ✅ 自动补全router.push({ path: '/invalid/path' }) // ❌ 类型错误
通过模块增强(module augmentation)填充 RouteNameMap 接口即可启用:
declare module '@meng-xi/uni-router' {interface RouteNameMap {pagesIndexIndex: { path: '/pages/index/index'; meta: { title: string; isTab: true } }pagesAboutAbout: { path: '/pages/about/about'; meta: { title: string } }}}
API 速查
核心
createRouter(options) | |
useRouter() | |
useRoute() | |
RouterLink |
Router 实例方法
router.push(location) | Promise<NavigationResult> | |
router.replace(location) | Promise<RouteLocation> | |
router.relaunch(location) | Promise<RouteLocation> | |
router.back(delta?, animation?) | Promise<void> | |
router.beforeEach(guard) | () => void | |
router.beforeResolve(guard) | () => void | |
router.afterEach(guard) | () => void | |
router.onRouteChange(listener) | () => void | |
router.onError(handler) | () => void | |
router.syncRoute() | void | |
router.resolve(location) | RouteLocation | |
router.getRoutes() | RouteConfig[] | |
router.hasRoute(name) | boolean |
RouterOptions
routes | RouteConfig[] | ||
strict | boolean | true | |
interceptUniApi | boolean | false | |
guardTimeout | number | 10000 |
RouteConfig
path | string | |
name | string | |
meta | RouteMeta | |
beforeEnter | NavigationGuard | NavigationGuard[] |
NavigationResult
push() 的返回值,继承 RouteLocation,额外包含 eventChannel:
path | string | |
name | string | undefined | |
meta | RouteMeta | |
query | Record<string, string> | |
fullPath | string | |
eventChannel | EventChannel | undefined |
与 pages.json 的关系
uni.navigateTo | pushreplace / relaunch / back | |
beforeEach | ||
meta | ||
name | ||
animationmeta.animation | ||
eventseventChannel |
快速开始
1. 安装
pnpm add @meng-xi/uni-router2. 配置路由
// src/router.config.tsimport type { RouteConfig } from '@meng-xi/uni-router'const routes: RouteConfig[] = [{ path: 'pages/index/index', name: 'home', meta: { isTab: true, title: '首页' } },{ path: 'pages/about/about', name: 'about', meta: { title: '关于' } },{ path: 'pages/login/login', name: 'login', meta: { title: '登录' } },{ path: 'pages/admin/admin', name: 'admin', meta: { requireAuth: true } }]export default routes
3. 创建路由器
// src/main.tsimport { 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,interceptUniApi: true,guardTimeout: 15000})// 注册全局守卫router.beforeEach((to, from, next) => {if (to.meta.requireAuth && !isLoggedIn()) {next({ name: 'login' })} else {next()}})router.afterEach(to => {if (to.meta.title) {uni.setNavigationBarTitle({ title: to.meta.title as string })}})export function createApp() {const app = createSSRApp(App)app.use(router)return { app }}
4. 在页面中使用
<template><view><text>当前路径: {{ route.path }}</text><button @click="goAbout">跳转关于</button><button @click="goAboutWithEvent">跳转并通信</button><button @click="goBack">返回</button></view></template><scriptsetup>import { useRouter, useRoute } from '@meng-xi/uni-router'const router = useRouter()const route = useRoute()async function goAbout() {try {await router.push({ name: 'about', query: { from: 'home' } })} catch (e) {console.log('导航失败:', e.code)}}async function goAboutWithEvent() {const result = await router.push({path: '/pages/about/about',events: {replyFromAbout: (data) => {console.log('收到关于页回复:', data)}}})// 向关于页发送消息result.eventChannel?.emit('fromHome', { msg: '你好,关于页!' })}async function goBack() {await router.back()}</script>
平台兼容性
版本演进
总结
@meng-xi/uni-router 为 uni-app 开发者提供了一套完整的路由管理方案:
- vue-router 风格 API
:零学习成本,上手即用 - 完整的守卫体系
:beforeEach / beforeResolve / afterEach / beforeEnter,支持重定向和超时保护 - 页面间通信
:EventChannel 双向通信,编程式和声明式均支持 - 导航动画
:三种使用方式,优先级清晰,仅 App 端生效 - uni API 拦截
:确保守卫始终生效,无论通过何种方式发起导航 - TypeScript 类型提示
:路由名称和路径自动补全 - 错误处理体系
:完整的错误码和全局错误捕获 - 组合式 API
:useRouter / useRoute,响应式路由状态 - Options API
:$router / $route 全局属性 - 路由状态同步
:处理浏览器后退、物理返回键等场景
夜雨聆风