小程序列表页每次返回都闪骨架屏?onShow 三态刷新策略
写了几年小程序,见过最普遍的一个性能 bug 不是复杂算法,而是一行习以为常的代码——
onShow() {
this.setData({ page: 1, list: [], pageReady: false });
this.getList();
}这段代码几乎成了行业模板,但它每次用户从详情页返回列表时,都会触发骨架屏闪烁 + 完整网络请求。用户刚看了一圈详情啥也没改,回来愣是要等一次加载——体感很割裂,QPS 也白白浪费。
现象
- 列表页 A → 详情页 B → 返回 A,必现骨架屏动画 +
GET /list?page=1请求 - 用户在 A 设置好的筛选条件(标签、关键词、排序)虽然数据上保留,但"整页抖了一下"
- 监控面板显示:快速 A↔B 来回三次 = 三条完全相同的列表请求,服务端差点限流
根因
onShow 触发场景有三种:
- 首次冷启动(onLoad 之后)
- App 从后台切回前台
- 从下一级页面 navigateBack 返回
场景 3 是日常使用的主路径,而列表数据在用户没做任何写操作的情况下根本没变。无脑 setData({list:[]}) + getList 等于把所有"返回"都当首次加载对待。
坑:onShow 不能区分「有没有必要刷新」
onShow 是纯粹的时机钩子,它知道"我被展示了",但不知道"上一级页面有没有写操作"。无差别重置 list 会:
- 触发骨架屏闪烁(
pageReady=false+ 骨架屏条件渲染) - 丢失 scroll-view 滚动位置(list 清空后滚到顶部)
- 制造大量重复请求,碰上慢网或限流就 UI 卡死
正解
用三个实例变量(放 this._xxx,不放 data)标记页面所处语义,详情页写操作后主动给上一页打标。
列表页
Page({
// 放实例变量,不触发 setData / Observer
_firstShow: true,
_needRefresh: false,
_optionsLoaded: false,
onShow() {
if (this._firstShow) {
this._firstShow = false;
this._loadStaticOptions(); // 静态选项只加载一次
this.getList(); // 首次拉数据
return;
}
if (this._needRefresh) {
this._needRefresh = false;
this.getList(); // 有变更才刷,不清 list,不闪骨架
return;
}
// 无变更返回:什么都不做
},
getList(reset = false) {
if (reset) {
this.setData({ page: 1, list: [], pageReady: false });
}
// ... 发请求
},
onPullDownRefresh() {
this.getList(true); // 下拉刷新才 reset
},
changeSort(e) {
this.setData({ sort: e.currentTarget.dataset.sort });
this.getList(true); // 筛选变化才 reset
},
_loadStaticOptions() {
if (this._optionsLoaded) return;
// ... 加载静态下拉选项
this._optionsLoaded = true;
},
});详情页:写操作后主动打标
onSaveSuccess() {
// 所有影响列表的写操作(保存、删除、改标签)都打标
const pages = getCurrentPages();
if (pages.length >= 2) {
const prev = pages[pages.length - 2];
if (prev) prev._needRefresh = true;
}
wx.navigateBack();
},正解:三态变量 + 详情页主动告知
| 场景 | _firstShow | _needRefresh | 行为 |
|---|---|---|---|
| 首次打开 | true | false | 拉静态选项 + getList |
| 返回(无写操作) | false | false | 什么都不做 |
| 返回(B 有写操作) | false | true | getList(不清 list,不闪骨架) |
| 下拉刷新 / 筛选变化 | — | — | getList(true),在各自 handler 里调 |
实例变量的好处:不触发 setData diff,不参与 Observer,Page 实例生命周期内一直保留。
一句话外卖
onShow只负责时机,刷不刷由语义决定:首次冷启、有写操作返回、无变更返回三态分开处理,详情页主动打标而不是列表页被动猜。