全面解析uni-router v1.2.0功能

本文基于 v1.2.0 版本,全面介绍 @meng-xi/uni-router 的设计理念与完整功能。

为什么需要 uni-router?

uni-app 原生路由系统基于 pages.json 静态配置,导航通过 uni.navigateTo / uni.redirectTo / 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.navigateBack 执行,不绕过 uni-app 的页面管理机制,确保跨平台行为一致。


功能全景

一、路由导航

push --- 导航到新页面
typescript 复制代码
// 路径字符串
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 --- 替换当前页面
typescript 复制代码
await router.replace('/pages/login/login')
await router.replace({ name: 'home' })

对应 uni.redirectTo。替换 TabBar 页面时自动切换为 uni.switchTab(会关闭所有非 Tab 页面)。

back --- 返回上一页
typescript 复制代码
await router.back() // 返回上一页
await router.back(2) // 返回两级
await router.back(1, { type: 'slide-out-left' }) // 指定动画

back() 执行完整的守卫链,守卫可以中止或重定向返回操作。若未指定动画参数,将使用目标页面的 meta.animation 作为默认动画。

重复导航检测

push 到当前页面时自动拒绝并抛出 NAVIGATION_DUPLICATED 错误,避免重复入栈。

并发导航排队

多次并发导航自动排队,前一次完成后再执行下一次,避免页面栈混乱。


二、路由守卫

路由守卫是 uni-router 最核心的能力,提供完整的导航拦截机制。

全局前置守卫 --- beforeEach

在每次导航前执行,常用于登录验证:

typescript 复制代码
router.beforeEach((to, from, next) => {
	if (to.meta.requireAuth && !isLoggedIn()) {
		next({ name: 'login', query: { redirect: to.fullPath } })
	} else {
		next()
	}
})
全局解析守卫 --- beforeResolve

在所有前置守卫和路由独享守卫完成后执行,适合需要确保所有守卫都已通过的场景:

typescript 复制代码
router.beforeResolve((to, from, next) => {
	// 所有前置守卫已通过,导航即将执行
	next()
})
全局后置钩子 --- afterEach

在导航完成后执行,不影响导航结果,适合埋点、标题设置等操作:

typescript 复制代码
router.afterEach((to, from) => {
	if (to.meta.title) {
		uni.setNavigationBarTitle({ title: to.meta.title as string })
	}
})
路由独享守卫 --- beforeEnter

在路由配置中定义,仅对该路由生效:

typescript 复制代码
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):

typescript 复制代码
router.beforeEach((to, from, next) => {
	if (to.meta.requireAuth && !isLoggedIn()) {
		next({ name: 'login' }) // 重定向到登录页
	} else {
		next()
	}
})
守卫超时保护

通过 guardTimeout 配置项(默认 10000ms),防止守卫未调用 next() 导致导航永久挂起:

typescript 复制代码
const router = createRouter({
	routes,
	guardTimeout: 15000 // 15 秒超时
})

三、命名路由

通过 name 字段进行导航,避免硬编码路径字符串:

typescript 复制代码
// 路由配置
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 标识、导航动画等自定义数据:

typescript 复制代码
interface RouteMeta {
	title?: string // 页面标题
	isTab?: boolean // 是否为 TabBar 页面
	requireAuth?: boolean // 是否需要登录认证
	animation?: NavigationAnimation // 默认导航动画(仅 App 端)
	[key: string]: unknown // 自定义扩展字段
}

使用示例:

typescript 复制代码
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 } }
]

五、导航动画

v1.2.0 新增完整的页面切换动画支持,仅 App 端生效,其他平台自动忽略。

typescript 复制代码
interface NavigationAnimation {
	type: UniAnimationType // 动画类型
	duration?: number // 持续时间(ms),默认 300
}
UniAnimationType --- 完整动画类型
显示动画(navigateTo) 关闭动画(navigateBack)
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
auto / none auto / none
三种使用方式

1. 导航时传入动画参数

typescript 复制代码
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)

typescript 复制代码
const routes = [{ path: 'pages/about/about', name: 'about', meta: { animation: { type: 'fade-in' } } }]

3. RouterLink 声明式动画

vue 复制代码
<RouterLink to="/pages/about/about" :animation="{ type: 'slide-in-bottom' }">
  底部滑入
</RouterLink>
动画优先级
复制代码
push/replace 调用时传入 > meta.animation > uni 默认值

六、uni API 拦截

通过 interceptUniApi 选项拦截原生导航 API,确保路由守卫始终生效:

typescript 复制代码
const router = createRouter({
	routes,
	interceptUniApi: true // 拦截 uni.navigateTo 等原生 API
})

启用后,以下调用将被拦截并转由路由器处理:

typescript 复制代码
// 这两种方式等价,都会经过守卫链
uni.navigateTo({ url: '/pages/about/about' })
router.push('/pages/about/about')
拦截原理
  • 通过 uni.addInterceptor 注册拦截器
  • 路由器内部发起的 API 调用通过计数器标记放行,避免重复执行守卫
  • 外部调用被拦截后,阻止原始 API 执行,转由 router.push/replace/back 执行完整守卫链
  • 低版本小程序基础库兼容:修改 args.url 为空字符串,防止忽略返回值继续执行
拦截范围
API 拦截后行为
uni.navigateTo router.push
uni.redirectTo router.replace
uni.switchTab router.push
uni.navigateBack router.back

七、组合式 API

useRouter --- 获取路由器实例
typescript 复制代码
import { useRouter } from '@meng-xi/uni-router'

const router = useRouter()
await router.push('/pages/about/about')

必须在 Vue 组件的 setup() 中调用,通过 Vue 的 inject 机制获取路由器实例。

useRoute --- 获取响应式路由位置
typescript 复制代码
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 缓存避免重复创建。


基于 uni-app navigator 封装的声明式导航组件:

vue 复制代码
<template>
	<!-- 路径跳转 -->
	<RouterLink to="/pages/about/about">关于页面</RouterLink>

	<!-- 命名路由 + 替换模式 -->
	<RouterLink :to="{ name: 'about' }" replace>替换导航</RouterLink>

	<!-- 带动画参数 -->
	<RouterLink to="/pages/about/about" :animation="{ type: 'fade-in' }"> 淡入动画 </RouterLink>

	<!-- 错误处理 -->
	<RouterLink :to="{ name: 'admin' }" @error="onNavError">管理后台</RouterLink>
</template>

<script setup>
import { RouterLink } from '@meng-xi/uni-router/components/RouterLink.vue'

function onNavError(error) {
	console.log('导航失败:', error.code)
}
</script>
Props
Prop 类型 默认值 说明
to RouteLocationRaw - 目标路由位置
replace boolean false 是否使用替换模式
animation NavigationAnimation undefined 导航动画(仅 App 端)
hoverClass string 'navigator-hover' 按下时的样式类
hoverStopPropagation boolean false 阻止祖先节点点击态
hoverStartTime number 50 按住后出现点击态时间
hoverStayTime number 600 松开后点击态保留时间
Events
事件 参数 说明
error NavigationFailure 导航失败时触发

九、路由状态同步

当页面通过浏览器后退、物理返回键等非路由器方式切换时,路由器的 currentRoute 可能与实际页面不同步。syncRoute() 方法从 uni-app 页面栈中读取当前页面信息并更新路由状态:

typescript 复制代码
// 在每个页面的 onShow 生命周期中调用
import { onShow } from '@dcloudio/uni-app'
import { useRouter } from '@meng-xi/uni-router'

const router = useRouter()

onShow(() => {
	router.syncRoute()
})
onRouteChange --- 路由变化监听

注册路由状态变化监听器,导航完成和状态同步时都会触发:

typescript 复制代码
router.onRouteChange((to, from) => {
	console.log(`路由变化: ${from.path} → ${to.path}`)
})

afterEach 不同,onRouteChange 也会捕获 syncRoute() 触发的状态变化。


十、错误处理

完整的错误体系
typescript 复制代码
// RouterError --- 路由错误基类
class RouterError extends Error {
	readonly code: RouterErrorCode
}

// NavigationFailure --- 导航失败,包含来源和目标信息
class NavigationFailure extends RouterError {
	readonly to: RouteLocation
	readonly from: RouteLocation
	readonly cause?: unknown
}
错误码
错误码 说明
NAVIGATION_ABORTED 导航被守卫中止或守卫超时
NAVIGATION_CANCELLED 导航被取消(守卫异常或重定向超限)
NAVIGATION_DUPLICATED 重复导航到当前位置
ROUTE_NOT_FOUND 未找到匹配的路由
NAVIGATION_API_ERROR uni 导航 API 调用失败
SETUP_ERROR 路由器初始化或使用方式错误
全局错误捕获
typescript 复制代码
router.onError((error, to, from) => {
	if (error.code === 'NAVIGATION_ABORTED') {
		console.log('导航被中止')
	}
})

十一、TypeScript 类型提示

配合 @meng-xi/vite-plugin 自动生成的类型声明,为路由导航提供类型安全:

typescript 复制代码
// 路由名称自动补全
router.push({ name: 'pagesIndexIndex' }) // ✅ 自动补全
router.push({ name: 'invalidName' }) // ❌ 类型错误

// 路径自动补全
router.push({ path: '/pages/index/index' }) // ✅ 自动补全
router.push({ path: '/invalid/path' }) // ❌ 类型错误

通过模块增强(module augmentation)填充 RouteNameMap 接口即可启用:

typescript 复制代码
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 速查

核心

API 说明
createRouter(options) 创建路由器实例
useRouter() 获取路由器实例(组合式 API)
useRoute() 获取响应式路由位置(组合式 API)
RouterLink 声明式导航组件

Router 实例方法

方法 说明
router.push(location) 导航到新页面
router.replace(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 拦截原生导航 API
guardTimeout number 10000 守卫超时时间(ms)

与 pages.json 的关系

职责 pages.json uni-router
页面注册 必须声明 不负责
路由导航 uni.navigateTo push / replace / back
路由守卫 不支持 beforeEach
路由元信息 不支持 meta 字段
命名路由 不支持 name 字段
导航动画 手动传参 animation + meta.animation

快速开始

1. 安装

bash 复制代码
pnpm add @meng-xi/uni-router

2. 配置路由

typescript 复制代码
// src/router.config.ts
import 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. 创建路由器

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,
	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. 在页面中使用

vue 复制代码
<template>
	<view>
		<text>当前路径: {{ route.path }}</text>
		<button @click="goAbout">跳转关于</button>
		<button @click="goBack">返回</button>
	</view>
</template>

<script setup>
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 goBack() {
	await router.back()
}
</script>

平台兼容性

平台 支持情况
uni-app (Vue 3) ✅ 完全支持
uni-app H5 (Safari / Chrome) ✅ 完全支持
uni-app App (Android / iOS) ✅ 完全支持(动画仅 App 端生效)
uni-app 小程序(微信/支付宝/百度/字节等) ✅ 完全支持
uni-app x Web ✅ 支持
uni-app x 小程序 ✅ 支持
uni-app x App (Android / iOS / Harmony) ❌ 不支持(无 JS 引擎,需 UTS 重写)

总结

@meng-xi/uni-router 为 uni-app 开发者提供了一套完整的路由管理方案:

  • vue-router 风格 API:零学习成本,上手即用
  • 完整的守卫体系:beforeEach / beforeResolve / afterEach / beforeEnter,支持重定向和超时保护
  • 导航动画:三种使用方式,优先级清晰,仅 App 端生效
  • uni API 拦截:确保守卫始终生效,无论通过何种方式发起导航
  • TypeScript 类型提示:路由名称和路径自动补全
  • 错误处理体系:完整的错误码和全局错误捕获
  • 组合式 API:useRouter / useRoute,响应式路由状态
  • 路由状态同步:处理浏览器后退、物理返回键等场景

如果你在 uni-app 项目中需要路由守卫、命名路由、元信息管理等能力,@meng-xi/uni-router 是你的最佳选择。

相关推荐
Yiyaoshujuku1 小时前
化学谱图数据API接口,数据字段一览!
linux·服务器·前端
雮尘2 小时前
LangGraph 与 LangSmith 入门教程(JS/TS 版)
前端·人工智能·langchain
英勇无比的消炎药2 小时前
新手必看玩转TinyRobot一定要避开这些坑
前端·vue.js
持敬chijing2 小时前
Web渗透之前后端漏洞-文件上传漏洞-过滤绕过与配置文件漏洞-条件竞争漏洞
前端·安全·web安全·网络安全·网络攻击模型·安全威胁分析
尼斯湖皮皮怪2 小时前
iceCoder:验收门控深度分析
前端·agent
周庆猛2 小时前
Babylon.js 多灯场景在 Windows 上报错:VERTEX shader uniform block count exceeds GL_MAX_VE
前端·数据可视化
胡志辉2 小时前
深入浅出理解浏览器事件循环:从一道输出题讲到 Chrome 源码
前端·javascript·面试
槑有老呆2 小时前
用 Bun 写一个 RESTful TodoList,顺便把面向接口编程整明白
前端
英勇无比的消炎药2 小时前
别再盲目混用AI组件库和传统组件库差距原来这么大
前端·vue.js