在uni-app开发电商类应用时,页面间的数据同步是个高频痛点。想象一个典型场景:用户浏览商品列表页(A),点击某个商品进入详情页(B),在B页点击"收藏"按钮后返回A页。此时如果列表页的收藏状态没有更新,用户会困惑"我刚才的操作生效了吗?"——这种体验就像在超市购物,收银员告诉你支付成功了,但购物车里的商品却纹丝不动。
传统解决方案有两种极端:要么完全不管数据同步,导致状态不同步;要么粗暴地重新加载整个页面,浪费流量且让用户看到明显的闪烁。我在早期项目中就犯过第二个错误:当时在navigateBack的success回调里直接调用prevPage.onLoad(),结果用户每次返回都会看到整个页面重新渲染的加载动画,产品经理当场就把这个方案毙掉了。
很多新手会混淆onLoad和onShow的触发时机。实测一个典型流程:
关键结论:onShow在每次页面可见时都会触发,而onLoad仅在组件创建时执行一次。这就是为什么把刷新逻辑放在onShow里更合理——它既保证了返回页面时的数据更新,又避免了重复执行初始化代码。
直接无脑在onShow里调接口会有性能隐患。我推荐这种优化方案:
javascript复制data() {
return {
lastRefreshTime: 0
}
},
onShow() {
// 距离上次刷新超过30秒才重新请求
if(Date.now() - this.lastRefreshTime > 30000) {
this.refreshHandler()
}
},
methods: {
refreshHandler() {
uni.request({
// 接口配置
success: () => {
this.lastRefreshTime = Date.now()
}
})
}
}
这个时间阈值可以根据业务调整:商品列表可能30秒,秒杀页面可能需要缩短到5秒。我在金融类App中甚至见过更精细的控制——根据网络环境动态调整刷新频率。
原始方案中直接在success回调里触发onLoad有个隐患:会执行整个页面的初始化逻辑。更优雅的做法是利用页面栈获取实例,直接调用自定义刷新方法:
javascript复制// B页面的返回操作
uni.navigateBack({
delta: 1,
success: () => {
const pages = getCurrentPages()
const prevPage = pages[pages.length - 1]
if(prevPage.$vm.refreshHandler) {
prevPage.$vm.refreshHandler()
}
}
})
注意这里用$vm获取Vue实例的写法,比直接调用onLoad更精准。我在实际项目中还遇到过页面栈被意外修改的情况,所以建议加上防御性判断:
javascript复制if(pages.length > 0 && prevPage.$vm) {
// 安全操作
}
对于更复杂的场景(比如需要传递修改的数据项),可以用全局事件总线:
javascript复制// main.js
Vue.prototype.$eventBus = new Vue()
// B页面
this.$eventBus.$emit('update-fav', {id: 123, fav: true})
// A页面
onShow() {
this.$eventBus.$on('update-fav', this.handleFavUpdate)
},
onHide() {
this.$eventBus.$off('update-fav')
}
这种方案适合频繁更新的场景,比如购物车数量变化。不过要注意及时销毁事件监听,否则会导致内存泄漏。曾经有个H5项目就因为这个原因导致页面越来越卡。
在大型项目中,建议把核心状态抽离到vuex。以收藏功能为例:
javascript复制// store.js
state: {
favMap: {}
},
mutations: {
UPDATE_FAV(state, {id, status}) {
Vue.set(state.favMap, id, status)
}
}
// B页面
methods: {
handleFav() {
this.$store.commit('UPDATE_FAV', {
id: this.productId,
status: !this.isFaved
})
}
}
// A页面
computed: {
listData() {
return this.rawList.map(item => ({
...item,
isFaved: this.$store.state.favMap[item.id]
}))
}
}
配合vuex-persistedstate插件还能实现本地持久化。我在一个跨境电商项目中用这个方案,即使应用完全退出再打开,用户的收藏状态也能保持。
对于快速连续触发onShow的情况(比如安卓物理返回键+手势返回),需要增加防抖处理:
javascript复制import { debounce } from 'lodash-es'
export default {
methods: {
refreshHandler: debounce(function() {
this.loading = true
uni.request({
complete: () => {
this.loading = false
}
})
}, 500)
}
}
同时建议在模板中添加优雅的加载状态:
html复制<view v-if="loading" class="skeleton-loader">
<!-- 骨架屏元素 -->
</view>
<view v-else>
<!-- 正常内容 -->
</view>
这些细节处理能让用户体验提升一个档次。有次版本更新后,客户特意发邮件表扬"页面切换变得特别顺滑",其实我们只是在这些不起眼的地方多花了点心思。