前情
uni-app是我比较喜欢的跨平台框架,它能开发小程序/H5/APP(安卓/iOS),重要的是对前端开发友好,自带的IDE可视化的运行和打包也让开发体验也非常棒,公司项目就是主推uni-app,为了解决页面数据更新操作,我实现了一套页面数据更新方案
为什么我要专门针对数据更新做方案
目前我遇到一种场景,我当前在订单详情页,详情页会展示当前订单的一些数据,特别是订单状态,同时会显示一个申请售后的按钮,此时我点击申请售后跳转售后提交页并成功发起了售后,我再执行back操作,订单详情页状态还是停留在前一个状态,同时申请售后按钮也还在,用户再点击又会跳售后申请页,再发起就会报错,因为服务端做了验证
其实像类似这样的场景有很多,如订单列表页有一个待支付订单,我点击订单跳转到订单详情完成支付再回来发现订单还在待支付状态,面对这种种给人不太好的体验,我在开发联调的时候忍了很久,一直在想怎么解决这种尴尬现象
常见方案
- 通过uni-app的事件通知机制
在前台页面做了一些操作,如果想更新后台的页面数据,后台页面做事件监听,前台页面发起事件通知,通知后台页面做数据更新
jsx
// 后台页面
// 在页面销毁的时候记得清除事件监听,此处使用的事件名是update,真正项目最好每一个页面都唯一
uni.$on('update', function(data) {
console.log('监听到事件来自 update,携带参数 msg 为:' + data.msg);
// 这里发起请求
});
// 前台页面
uni.$emit('update', { msg: '页面更新' });
- 全局状态管理
就是把数据放全局状态管理,如vuex/pinia,如果是vue3项目就直接用pinia,前台页面直接调用全局状态管理的方法以达到更新后台页面的的数据
- onShow生命钩子里暴力拉取更新
uni-app为页面引入了onShow生命钩子,每次页面从后台切到前台都会触发,最暴力的做法就是每次onShow触发的时候做接口请求,这样确实是可以解决数据更新延后的问题,但是问题就是会发生很多无用的接口请求
- 给页面引入下拉刷新
再有一种方法就是给当前页实现下拉刷新,个人觉得也是不错的方案,是现在很多主流项目在用的,像京东、拼多多、淘宝等,但这主要用在像商品列表,订单列表等,当然如果你项目中有实现下拉刷新,那对于我这个数据更新方案就可有可无了,加上也是不错的,我感觉二者可以并存
我使用的方案
上面这些方法都是可取的,主要看个人选择吧,我的方案也是依赖生命周期钩子, 但是做了一点点优化,我一直都在想有什么方式我可以通知在后台的页面做更新了,我首先想到的就是通过全局状态管理或者事件通知,我此时在想真的需要用到全局状态管理和事件通知么,有没有一种更简单的方式了,于是几番思索后想到了如下方案:
我的方案很简单,就是全局管理一个对象,对象记录需要做页面数据更新的key,同时暴露一些方法可以对key进行增删重置操作,同时封装一个hooks,在页面的onShow生命钩子里检查当前页是否在需要做数据更新的key里,如果是那就做数据更新,同时我还做了一个小尝试,如果onHide和onShow中间间隔达到一定时间,这里我定义为30s,也做下数据更新,这只是我自己加的,也难说是好是坏,先这样用着,如果后续发现不是特别理想再修改也不迟
全局对象
下面是我定义的全局对象,pagesKey记录需要做数据更新的 key,还有一个离开需要数据更新的最小时间,再暴露一些必要方法
jsx
// 全局页面key管理
export const pageToBeUpdated = {
pagesKey: [],
// 离开30s后,再回来触发更新
delayTime: 30000,
add(pageKey) {
if (!this.pagesKey.includes(pageKey)) {
this.pagesKey.push(pageKey)
}
},
remove(pageKey) {
const index = this.pagesKey.indexOf(pageKey)
if (index !== -1) {
this.pagesKey.splice(index, 1)
}
},
reset() {
this.pagesKey = []
},
isExist(pageKey) {
return this.pagesKey.includes(pageKey)
},
}
封装的hook
jsx
// @ts-expect-error
import { onShow, onLoad, onUnload, onHide } from '@dcloudio/uni-app'
import { pageToBeUpdated } from '@/config/global'
/**
* 页面更新
* @param pageKey 页面key
* @param callback 回调函数,一般是页面更新key
*/
export const usePageUpdate = (pageKey: string, callback?: () => void) => {
let leaveTime = Date.now()
// 页面显示时,判断当前页面是否在需要更新的页面key中,如果在,证明当前页面是从后台返回的,应该触发回调更新函数
onShow(() => {
if (pageToBeUpdated.isExist(pageKey)) {
callback && callback()
return
}
// 如果离开超过pageToBeUpdated.delayTime时间,再回来就触发更新
if (Date.now() - leaveTime > pageToBeUpdated.delayTime) {
callback && callback()
}
})
// 页面隐藏时,证明当前页面也不在前台了,也应该移除排当前页面的key,避免重复触发
onHide(() => {
leaveTime = Date.now()
pageToBeUpdated.remove(pageKey)
})
// 如果页面触发了onLoad,证明是初次进入,应该去排除页面key,避免重复触发
onLoad(() => {
pageToBeUpdated.remove(pageKey)
})
// 页面卸载时,证明当前页面也不在后台了,也应该移除排当前页面的key,避免重复触发,其实可有可无,因为onLoad的时候就已经移除了
onUnload(() => {
pageToBeUpdated.remove(pageKey)
})
}
其中做了一点优化,如果当前页面onLoad钩子触发了,那就不是从后台切回了,此时就要移除当前页面的key,避免页面多次触发数据请求,因为我当前项目基本所有页面的接口请求都放在onLoad钩子里了
使用方式
在需要做数据更新的页面使用hook,关键示例代码如下:
jsx
import { usePageUpdate } from '@/hooks/pageUpdate'
usePageUpdate('rentalAfterSalesDetail', getOrderDetailRequest)
注:usePageUpdate第一个参数是当前页面的 key,要做到唯一,第二方法是当前页面拉取服务端数据并更新状态的方法
收集页面key,在需要的地方做页面key收集,等待页面检查,关键示例代码如下:
jsx
import { pageToBeUpdated } from '@/config/global'
pageToBeUpdated.add('rentalAfterSalesDetail')
注:在收集页如果你做了一些可能修改了运行在后台的页面状态的操作的时候,就可以做页面key收集
小结
此方案需要你全权把握数据更新,你要知道哪些操作可能会导致运行的后台的页面数据状态有改变,同时又有一个保底更新逻辑,离开页面多久触发更新,这个暂时我定为30s,像抖音/支付宝如果你小程序切到后台超过5分钟就会被杀掉了,而微信是30分钟,所以我选30s,应该是所有平台都是可兼容的了,此举我也难说是好是坏,暂时我项目就是这样用,但是至少可以解决一种问题,就是用户较长时间切到后台再回来保证数据是比较新的
这是我的 uni-app项目的后台数据页面更新方案,已使用在生产中,目前还没有发现什么问题,如果后续发现什么问题再做更新吧,解决方案千千万,世上没有最好,只有更好,如果你有更好的解决方案,可以分享出来,或者你发现此方案有什么问题,也可以提出来一起探讨