返回博客

onShow + onReady 同时拉数据,微信小程序页面首次加载 API 被调两次

给页面加"返回自动刷新"需求时,我习惯在 onShow 里调一次数据方法;而 onReady 里本来就有登录检查 + 初始化拉取。写完上线,打开 Network 面板一看——同一个接口连续打了两条请求,间隔不到 50ms。服务端日志也证实了:首次进页面,数据接口被调用了两次。

现象

  • 打开 DevTools 的 Network 面板,进入该页面的瞬间,同一个 API 端点出现两条紧挨着的请求
  • 页面偶尔出现短暂闪烁:先渲染一次,再刷新一次,内容抖了一下。
  • 离开页面再返回时,接口只打一次(行为正常)。

规律很明显:只在首次进入时重复,返回时不重复。

根因

微信小程序页面的生命周期顺序是:

首次进入:onLoad → onShow → onReady
tab 切换:onShow(仅此一个)
navigateBack:onShow(仅此一个)
wx.reLaunch:onLoad → onShow → onReady(完整循环)

关键反直觉点:onShowonReady 先触发

很多人(包括我)直觉上以为 onReady 是"渲染完成"、onShow 是"每次显示",所以在 onReady 做初始化、onShow 做刷新,看起来天经地义——实际上首次进入时两个钩子都会触发,而且 onShow 在前。

坑:onShow 和 onReady 在首次进入时都会执行

首次进入页面时,onLoad → onShow → onReady 三个钩子全部触发。如果 onShowonReady 都调用了同一个数据方法,就会产生两次请求。这个问题在"返回刷新"需求加进来之前不会暴露,所以往往是后期追加功能时才踩到。

正解

用一个实例标志位 _hasLoaded 区分「首次加载」和「后续 onShow」:

Page({
  onLoad(options) {
    // 解析参数,设置初始状态
  },
 
  onReady() {
    const that = this
    // 首次加载:做登录检查、权限弹窗等初始化,然后拉数据
    app.checkLogin(function () {
      that.loadData()
    })
  },
 
  onShow() {
    // 只在「返回」时刷新,首次进入由 onReady 负责
    if (this._hasLoaded) {
      this.loadData()
    }
  },
 
  loadData() {
    const that = this
    api.getData(params).then(function (res) {
      that._hasLoaded = true  // 成功后才打标,失败时 onShow 仍会重试
      that.setData({ /* ... */ })
    })
  },
})

三个关键决策:

  1. _hasLoaded异步成功回调里赋值,而不是在 onReady 入口。这样如果首次加载失败(网络错误、登录跳转),用户返回后 onShow 还会再试一次,不会静默跳过。
  2. onReady 负责首次加载,onShow 负责返回刷新——各司其职,不重叠。
  3. 标志位挂在实例上(this._hasLoaded),不放 data 里,避免触发不必要的 setData 渲染。
正解:_hasLoaded 实例标志位区分首次与后续

onReady 无条件拉数据(首次),onShow 检查 _hasLoaded 后才拉(返回刷新)。标志位在异步成功回调里设置,保证失败可重试。这个模式适用于原生 Page、Taro、uni-app,以及 Omix create.Page(store, {...}) 封装——底层都是标准 Page 构造器。

附:入场动画闪烁的同类坑

如果页面有 CSS 入场动画(opacity 0→1translateY)通过 pageReady 标志位驱动,onShow 每次把 pageReady 重置为 false 再异步设回 true,用户返回时所有动画元素会短暂消失再淡入——视觉上像白屏了一下。

解法同理,加一个 _hasShown 标志:

onShow() {
  const resetData = { /* 其他需要重置的字段 */ }
 
  // 只在首次进入时重置入场动画
  if (!this._hasShown) {
    resetData.pageReady = false
    this._hasShown = true
  }
 
  this.setData(resetData)
  // 加载数据后再把 pageReady 设回 true
}

_hasLoaded 控制「要不要请求数据」,_hasShown 控制「要不要重置动画」,两个问题独立,可能同时需要。

一句话外卖

微信小程序生命周期顺序是 onLoad → onShow → onReadyonShow 先于 onReady 执行;「返回刷新」逻辑加进 onShow 之前,先用 _hasLoaded 标志位把首次和后续区分开。