本文基于 v1.3.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 } })
push 自动根据 meta.isTab 选择 uni.navigateTo(普通页面)或 uni.switchTab(TabBar 页面),开发者无需手动判断。
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不进行重复导航检测。因为清栈场景下目标页面可能就是当前页面(如"返回首页"),拒绝此类导航没有意义。
并发导航排队
多次并发导航自动排队,前一次完成后再执行下一次,避免页面栈混乱。
二、路由守卫
路由守卫是 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' })}}]
守卫执行顺序
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 秒超时})
三、命名路由
通过 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 缓存避免重复创建。
八、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="{ name: 'admin' }" @error="onNavError">管理后台</RouterLink></template><scriptsetup>import { RouterLink } from '@meng-xi/uni-router/components/RouterLink.vue'function onNavError(error) {console.log('导航失败:', error.code)}</script>
Props
to | RouteLocationRaw | ||
replace | boolean | false | |
relaunch | boolean | false | |
animation | NavigationAnimation | undefined | |
hoverClass | string | 'navigator-hover' | |
hoverStopPropagation | boolean | false | |
hoverStartTime | number | 50 | |
hoverStayTime | number | 600 |
Events
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('导航被中止')}})
十一、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) | |
router.replace(location) | |
router.relaunch(location) | |
router.back(delta?, animation?) | |
router.beforeEach(guard) | |
router.beforeResolve(guard) | |
router.afterEach(guard) | |
router.onRouteChange(fn) | |
router.onError(handler) | |
router.syncRoute() | |
router.resolve(location) | |
router.getRoutes() | |
router.hasRoute(name) |
RouterOptions
routes | RouteConfig[] | ||
strict | boolean | true | |
interceptUniApi | boolean | false | |
guardTimeout | number | 10000 |
与 pages.json 的关系
uni.navigateTo | pushreplace / relaunch / back | |
beforeEach | ||
meta | ||
name | ||
animationmeta.animation |
快速开始
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="goHome">返回首页</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 goHome() {// 关闭所有页面并打开首页await router.relaunch({ name: 'home' })}async function goBack() {await router.back()}</script>
平台兼容性
版本演进
back()syncRoute() query 比较、onUnmount 兼容 | |
getCurrentPages() | |
relaunchrelaunch prop、reLaunch 拦截 |
总结
@meng-xi/uni-router 为 uni-app 开发者提供了一套完整的路由管理方案:
- vue-router 风格 API
: push/replace/relaunch/back,零学习成本 - 完整的守卫体系
:beforeEach / beforeResolve / afterEach / beforeEnter,支持重定向和超时保护 - 导航动画
:三种使用方式,优先级清晰,仅 App 端生效 - uni API 拦截
:覆盖全部五种导航 API,确保守卫始终生效 - TypeScript 类型提示
:路由名称和路径自动补全 - 错误处理体系
:完整的错误码和全局错误捕获 - 组合式 API
:useRouter / useRoute,响应式路由状态 - 路由状态同步
:处理浏览器后退、物理返回键等场景
夜雨聆风