uni-app 页面路由与通信机制详解:跳转传参、EventChannel 与全局状态

uni-app 页面路由与通信机制详解:跳转传参、EventChannel 与全局状态

admin
2026-07-26 / 0 评论 / 5 阅读

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)
}

两个注意点:

  1. 参数会被编码:对象、数组必须先 encodeURIComponent(JSON.stringify(obj)),接收方再解码解析
  2. 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 的 eventssuccess 返回的 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

评论 (0)

取消
0:00