返回博客

小程序列表页每次返回都闪骨架屏?onShow 三态刷新策略

写了几年小程序,见过最普遍的一个性能 bug 不是复杂算法,而是一行习以为常的代码——

onShow() {
  this.setData({ page: 1, list: [], pageReady: false });
  this.getList();
}

这段代码几乎成了行业模板,但它每次用户从详情页返回列表时,都会触发骨架屏闪烁 + 完整网络请求。用户刚看了一圈详情啥也没改,回来愣是要等一次加载——体感很割裂,QPS 也白白浪费。

现象

  • 列表页 A → 详情页 B → 返回 A,必现骨架屏动画 + GET /list?page=1 请求
  • 用户在 A 设置好的筛选条件(标签、关键词、排序)虽然数据上保留,但"整页抖了一下"
  • 监控面板显示:快速 A↔B 来回三次 = 三条完全相同的列表请求,服务端差点限流

根因

onShow 触发场景有三种:

  1. 首次冷启动(onLoad 之后)
  2. App 从后台切回前台
  3. 从下一级页面 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 只负责时机,刷不刷由语义决定:首次冷启、有写操作返回、无变更返回三态分开处理,详情页主动打标而不是列表页被动猜。