uni-app 的页面机制和纯 Vue 项目差异很大:页面栈、tabBar 限制、生命周期钩子都带着小程序的影子。很多从 Web 转过来的开发者在"页面间怎么传值"这一步就开始踩坑。本文系统讲清 uni-app 的路由体系与页面通信的全部手段。
一、uni-app 的路由本质
uni-app 没有 vue-router,页面路由由 pages.json 声明,跳转靠 uni. API:
// pages.json
{
"pages": [
{ "path": "pages/index/index", "style": { "navigationBarTitleText": "首页" } },
{ "path": "pages/order/list", "style": {} },
{ "path": "pages/order/detail", "style": {} }
],
"tabBar": {
"list": [
{ "pagePath": "pages/index/index", "text": "首页" },
{ "pagePath": "pages/mine/mine", "text": "我的" }
]
}
}第一个坑就在这里:tabBar 页面只能用 uni.switchTab 跳转,用 uni.navigateTo 会静默失败,这是新手最常见的"跳转没反应"原因。
二、五种跳转 API 与页面栈
| API | 行为 | 页面栈变化 | 返回 |
|---|---|---|---|
uni.navigateTo | 保留当前页,跳转新页 | push | 可返回 |
uni.redirectTo | 关闭当前页,跳转新页 | replace | 不可返回当前页 |
uni.reLaunch | 关闭所有页面,跳转 | 清空后 push | 返回到首页 |
uni.switchTab | 跳转 tabBar 页 | 清空非 tab 页 | - |
uni.navigateBack | 返回上一页 | pop | - |
页面栈上限是 10 层。连续 navigateTo 超过 10 层会报错。常见于"查看详情 → 查看用户 → 查看详情"的无限循环场景,解决方案是深链入口用 redirectTo 替代 navigateTo,或在 onShow 里检测栈深度做收敛:
onShow() {
const pages = getCurrentPages()
if (pages.length >= 8) {
uni.redirectTo({ url: '/pages/order/detail?id=' + this.id })
}
}三、页面传参的三种方式
方式一:URL 查询参数(最常用)
uni.navigateTo({
url: '/pages/order/detail?id=1001&from=list'
})// 目标页面:onLoad 接收
onLoad(options) {
// options: { id: '1001', from: 'list' }
// 注意:所有值都是 string
this.orderId = Number(options.id)
}两个注意点:
- 参数会被编码:对象、数组必须先
encodeURIComponent(JSON.stringify(obj)),接收方再解码解析 - tabBar 页面接收参数:switchTab 不支持 URL 传参,只能走全局状态或事件
// 传对象
uni.navigateTo({
url: '/pages/filter/result?query=' + encodeURIComponent(JSON.stringify(filter))
})
// 接收
onLoad(options) {
this.query = JSON.parse(decodeURIComponent(options.query))
}方式二:EventChannel 事件通道(双向通信)
navigateTo 的 events 与 success 返回的 EventChannel 可以让两个页面直接通信,适合"选择收货地址"这类带回传数据的场景:
// 订单页:打开地址选择页,监听选择结果
uni.navigateTo({
url: '/pages/address/select',
events: {
// 监听地址选择页抛回的事件
selectAddress(address) {
this.address = address
}
},
success: (res) => {
// 向地址选择页传递数据
res.eventChannel.emit('init', { selectedId: this.address?.id })
}
})// 地址选择页
onLoad(options) {
const eventChannel = this.getOpenerEventChannel()
// 接收初始数据
eventChannel.on('init', (data) => {
this.selectedId = data.selectedId
})
this.eventChannel = eventChannel
},
methods: {
onSelect(address) {
// 通知上一页并返回
this.eventChannel.emit('selectAddress', address)
uni.navigateBack()
}
}相比全局状态,EventChannel 作用域仅限两个页面,不污染全局,是临时性页面间通信的最佳选择。
方式三:全局状态(跨页面、持久化)
跨页面返回时更新上一页数据
一个高频场景:列表页 → 编辑页,编辑完成后返回列表页需要刷新。可以用 getCurrentPages() 直接操作上一页实例:
// 编辑页:保存成功后
const pages = getCurrentPages()
const listPage = pages[pages.length - 2] // 上一页实例
listPage.$vm.refreshList() // 调用上一页的方法
uni.navigateBack()这种写法直接、无依赖,但要克制使用——页面耦合加深后维护成本上升。更规范的做法是全局状态 + 上一页 onShow 时检查脏标记:
// store/user.js(Pinia 方案)
import { defineStore } from 'pinia'
export const useAddressStore = defineStore('address', {
state: () => ({
selectedAddress: null,
dirty: false
})
})
// 编辑页保存成功后
const store = useAddressStore()
store.selectedAddress = address
store.dirty = true
// 列表页
onShow() {
const store = useAddressStore()
if (store.dirty) {
this.refreshList()
store.dirty = false
}
}四、全局状态管理选型
uni-app 支持 Vuex 和 Pinia。Vue 3 项目无脑选 Pinia:
// main.js
import { createSSRApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
app.use(createPinia())
return { app }
}// stores/cart.js
import { defineStore } from 'pinia'
export const useCartStore = defineStore('cart', {
state: () => ({
items: uni.getStorageSync('cart_items') || []
}),
actions: {
addItem(goods) {
this.items.push(goods)
// 小程序没有 localStorage,用 uni API 持久化
uni.setStorageSync('cart_items', this.items)
}
}
})关键差异:uni-app 运行在小程序端时没有 localStorage,必须用 uni.setStorageSync / uni.getStorageSync 做持久化。Pinia 持久化插件默认走 localStorage,小程序端会直接报错,需要自定义 storage 适配器。
另外两个轻量方案:
- globalData:App.vue 的
globalData字段 +getApp().globalData,适合放极简的配置型数据 - uni.$emit / uni.$on:全局事件总线,跨页面通知(如登录成功后刷新多个页面),但要在 onUnload 里
$off解绑,否则内存泄漏
// App.vue
export default {
globalData: {
theme: 'light',
version: '2.1.0'
}
}
// 任意页面
const app = getApp()
console.log(app.globalData.theme)五、页面生命周期速查
uni-app 页面生命周期在 Vue 钩子之外扩展了小程序特有的钩子:
| 钩子 | 触发时机 | 典型用途 |
|---|---|---|
| onLoad | 页面加载,可接收参数 | 解析跳转参数、首次请求 |
| onShow | 页面显示(含返回时) | 刷新数据、检查登录态 |
| onHide | 页面隐藏 | 暂停定时器 |
| onReady | 初次渲染完成 | 获取节点信息 |
| onReachBottom | 滚动到底部 | 分页加载 |
| onPullDownRefresh | 下拉刷新 | 刷新列表 |
| onShareAppMessage | 分享 | 定义分享内容 |
onLoad vs onShow 的选择:onLoad 只执行一次(携带参数),onShow 每次显示都执行。列表页的数据刷新逻辑放 onShow,参数解析放 onLoad。
总结
- tabBar 页面只能 switchTab,页面栈上限 10 层
- URL 传参全是 string,对象要 JSON + encodeURIComponent
- EventChannel 是"选择页回传数据"的标准方案,作用域干净
getCurrentPages()操作上一页实例简单直接,Pinia + 脏标记更规范- 小程序端持久化必须用 uni.setStorageSync,Pinia 持久化插件需要适配
下一篇我们讲网络请求封装——把 uni.request 改造成带拦截器、Token 无感刷新的企业级请求层。
评论 (0)