5.1 uni-app 路由系统概述
uni-app 不使用 Vue Router,而是有自己的路由体系。路由由 pages.json 中的 pages 数组集中管理,所有页面必须先在此注册,才能被访问。路由跳转通过 uni 全局对象的一系列 API 完成。
uni-app 维护一个页面栈,记录历史访问过的页面。栈的最顶层是当前显示的页面。不同的跳转方式对页面栈的影响不同,这直接决定了用户能否"返回"到上一页。小程序的页面栈深度最大为 10 层,超出后将无法继续 navigateTo。
5.2 路由 API 详解
navigateTo — 普通跳转(保留当前页)
// 基本用法
uni.navigateTo({
url: '/pages/detail/detail?id=123&type=goods'
})
// 使用 Promise 封装(推荐)
try {
await uni.navigateTo({ url: '/pages/detail/detail?id=123' })
} catch (err) {
console.error('跳转失败:', err)
}
// 在目标页面接收参数(onLoad 的 query 对象)
// pages/detail/detail.vue
onLoad((query) => {
const id = query?.id // '123'(注意是字符串类型)
const type = query?.type // 'goods'
})
通过 URL 传递的参数全部是字符串类型,包括数字和布尔值。如果需要传递 id=123(数字),在目标页面接收到的是字符串 "123",需要手动转换 Number(query.id)。复杂对象参数需要 JSON.stringify + JSON.parse 处理。
redirectTo — 重定向(关闭当前页)
// 关闭当前页面并跳转,用户无法通过返回键回到当前页
// 典型场景:登录成功后跳转首页、引导页跳转主页
uni.redirectTo({
url: '/pages/index/index'
})
reLaunch — 重启(清空所有页面栈)
// 关闭所有页面,打开目标页面(相当于重新启动到该页面)
// 典型场景:退出登录后回到登录页(确保用户无法返回需要登录的页面)
uni.reLaunch({
url: '/pages/login/login'
})
switchTab — 切换 tabBar 页面
// 跳转到 tabBar 页面,并关闭所有非 tabBar 页面
// 注意:tabBar 页面只能用此 API 跳转,不能用 navigateTo
uni.switchTab({
url: '/pages/index/index' // 不能带参数!
})
navigateBack — 返回上一页
// 返回上一页(默认返回 1 层)
uni.navigateBack()
// 返回多层(delta 为返回的层数)
uni.navigateBack({ delta: 2 })
// 获取当前页面栈(调试用)
const pages = getCurrentPages()
console.log('当前页面栈深度:', pages.length)
const currentPage = pages[pages.length - 1]
console.log('当前页面路径:', currentPage.route)
路由 API 对比总结
| API | 当前页面 | 页面栈 | 适用场景 |
|---|---|---|---|
navigateTo | 保留 | 入栈(+1) | 普通跳转详情页 |
redirectTo | 关闭 | 替换栈顶 | 登录成功跳转 |
reLaunch | 关闭 | 清空重建 | 退出登录 |
switchTab | 关闭非tab页 | 切换根页面 | 切换底部导航 |
navigateBack | 关闭 | 出栈(-delta) | 返回上一页 |
5.3 页面间通信
页面之间需要传递数据时,有多种方案可选:
方案一:URL 参数(简单数据)
// A 页面跳转,传简单参数
uni.navigateTo({ url: '/pages/b/b?id=42&name=苹果' })
// B 页面接收
onLoad((query) => {
const id = Number(query?.id) // 42
const name = decodeURIComponent(query?.name ?? '') // '苹果'
})
方案二:EventChannel(大数据 / 对象,推荐)
EventChannel 是 uni-app 提供的页面间通信通道,可以传递任意数据类型:
// A 页面跳转时创建事件通道
uni.navigateTo({
url: '/pages/b/b',
events: {
// 监听 B 页面向 A 页面发送的事件
selectItem(data: { item: Product }) {
console.log('B 页面选中了:', data.item)
selectedProduct.value = data.item
}
},
success(res) {
// A 向 B 发送初始数据
res.eventChannel.emit('initData', { userId: currentUser.value.id })
}
})
// B 页面接收数据
onLoad(() => {
const eventChannel = getCurrentPages()
.at(-1)?.getOpenerEventChannel()
// 监听 A 发来的初始数据
eventChannel?.on('initData', (data: { userId: number }) => {
console.log('收到用户 ID:', data.userId)
})
})
// B 页面向 A 发送选中结果(用户选完商品)
function confirmSelect(item: Product) {
const eventChannel = getCurrentPages().at(-1)?.getOpenerEventChannel()
eventChannel?.emit('selectItem', { item })
uni.navigateBack()
}
方案三:全局状态(Pinia)
对于需要在多个页面共享的状态(用户信息、购物车等),使用 Pinia store 是最清晰的方案。详见第 6 章。
5.4 路由拦截器实现
uni-app 没有内置的路由守卫(不同于 Vue Router 的 beforeEach),但可以通过重写路由 API 实现拦截逻辑:
// utils/router.ts — 带鉴权的路由工具
import { useUserStore } from '@/stores/user'
// 不需要登录即可访问的页面白名单
const WHITE_LIST = [
'/pages/login/login',
'/pages/index/index',
'/pages/register/register'
]
function checkAuth(url: string): boolean {
const path = url.split('?')[0]
if (WHITE_LIST.includes(path)) return true
const userStore = useUserStore()
return !!userStore.token
}
export function navigateTo(url: string, options?: UniApp.NavigateToOptions) {
if (!checkAuth(url)) {
// 未登录,跳转登录页,并记录原始目标
const redirectUrl = encodeURIComponent(url)
uni.navigateTo({
url: `/pages/login/login?redirect=${redirectUrl}`
})
return
}
uni.navigateTo({ url, ...options })
}
// 登录成功后跳回原始页面
export function afterLogin(query: Record<string, string>) {
const redirect = query?.redirect
if (redirect) {
uni.redirectTo({ url: decodeURIComponent(redirect) })
} else {
uni.switchTab({ url: '/pages/index/index' })
}
}
如果需要更完整的路由守卫能力,可以考虑 uni-router 或 uniapp-router 等社区库,它们参考了 Vue Router 的 API 设计,提供 beforeEach / afterEach 等钩子。
5.5 tabBar 高级配置
自定义 tabBar 组件
默认的 tabBar 样式有限,可以用自定义 tabBar 实现更丰富的视觉效果:
// pages.json
{
"tabBar": {
"custom": true, // 开启自定义 tabBar
"color": "#78716c",
"selectedColor": "#0ea5e9",
"list": [
{ "pagePath": "pages/index/index", "text": "首页" },
{ "pagePath": "pages/user/user", "text": "我的" }
]
}
}
<!-- custom-tab-bar/index.vue(必须在这个固定路径)-->
<template>
<view class="tab-bar">
<view
v-for="(item, index) in tabs"
:key="index"
class="tab-item"
:class="{ active: currentIndex === index }"
@tap="switchTo(index, item.pagePath)"
>
<image :src="currentIndex === index ? item.activeIcon : item.icon" />
<text>{{ item.text }}</text>
</view>
</view>
</template>
5.6 页面栈管理实战
某些场景需要对页面栈进行精确操作。例如:用户在 A → B → C 流程后,希望直接跳回 A 而非逐页返回:
// 方案一:reLaunch(最彻底,但会重新加载 A 页面)
uni.reLaunch({ url: '/pages/a/a' })
// 方案二:精确计算 delta 值
const pages = getCurrentPages()
const targetIndex = pages.findIndex(p => p.route === 'pages/a/a')
if (targetIndex !== -1) {
const delta = pages.length - 1 - targetIndex
uni.navigateBack({ delta })
}
// 向指定历史页面传数据(通过全局事件或 store)
const prevPage = pages[pages.length - 2] // 上一页
// 直接调用上一页的方法(仅 App/H5,小程序限制严格)
const prevVm = prevPage?.$vm as any
prevVm?.refreshData?.()
uni.navigateBack()
5.7 小结
- uni-app 有 5 种路由 API,选择时重点考虑对页面栈的影响
- tabBar 页面必须使用
switchTab跳转,不能使用navigateTo - URL 参数只能传字符串,复杂数据用 EventChannel 或 Pinia
- 路由拦截通过封装路由工具函数实现,白名单控制免登录页面
- 页面栈最大深度 10(小程序),开发时需注意防止栈溢出
getCurrentPages()可获取完整页面栈,用于精确控制返回层数