返回博客

Omix setData 之后派生值算出来是 0,根因是 this.data 还没同步

在一个微信小程序的积分兑换页里,我有个方法负责拉取礼品列表,拉完后立刻调 _updateCounts() 统计已兑换和已完成数量,最后渲染到页面顶部。逻辑看上去无懈可击:拉数据、存数据、算数据,顺序执行。但上线后发现数量栏目始终显示 0,而列表本身渲染完全正常,数据明明在那里。

现象

  • setData 把礼品列表写入 gift.list[0],WXML 里列表渲染正确,每条记录都在
  • _updateCounts()setData 调用后立即执行,读 this.data.gift.list 遍历统计
  • 打印 this.data.gift.list,拿到的是更新前的旧数组,length 为 0
  • 统计结果 exchangedcompleted 均为 0,最终 setData 写入页面的数字也是 0

根因

这个页面用的是 Omix store(create.Page(store, {...})),其底层通过 OBAA(一种 Object.observe polyfill)实现响应式绑定。

Omix 对 setData 做了包装:调用后,数据排队进入 WXML 渲染管线,但 this.data 的镜像不会同步刷新——尤其是路径式写法('gift.list[0]')这类深层键,OBAA 的回调是异步触发的,this.data 在当前调用帧内仍然是旧值。

标准 Page({ setData }) 不存在这个问题,因为它的 this.data 是同步更新的。Omix 引入的这一层包装让两者语义产生了微妙的分叉。

坑:Omix setData 之后 this.data 是旧值

在使用 create.Page(store, {...}) 的 Omix 页面里,setData 返回后 this.data 不保证已更新,对深层路径(如 gift.list[0])尤为明显。在 setData 后立即读 this.data 来做二次计算,拿到的可能是本次更新之前的快照。

正解

把派生值的计算内联到同一个 setData 调用里——从你手里刚处理好的 arr 直接算,而不是从 this.data 里再读一次。

// 错误写法:setData 之后另起方法读 this.data
loadGiftData() {
  api.getGifts(params).then(res => {
    const arr = processItems(res.data.list)
    this.setData({
      ['gift.list[' + (page - 1) + ']']: arr,
      'gift.loaded': true,
    })
    // BUG:this.data.gift.list 还是旧数组!
    this._updateCounts()
  })
}
 
_updateCounts() {
  const list = this.data.gift.list  // 读到的是旧值
  let exchanged = 0, completed = 0
  for (const page of list) {
    if (!page) continue
    for (const item of page) {
      item.status === '2' ? completed++ : exchanged++
    }
  }
  this.setData({ exchangedCount: exchanged, completedCount: completed }) // 全是 0
}
// 正确写法:从 arr 直接算,一次 setData 写入所有值
loadGiftData() {
  api.getGifts(params).then(res => {
    const arr = processItems(res.data.list)
 
    // 直接遍历刚处理好的 arr,不走 this.data
    let pageExchanged = 0, pageCompleted = 0
    for (const item of arr) {
      item.status === '2' ? pageCompleted++ : pageExchanged++
    }
 
    // 分页累加时,旧数据已稳定,可安全读取旧计数
    const isFirst = this.data.gift.currpage === 1
    const newExchanged = isFirst ? pageExchanged : this.data.exchangedCount + pageExchanged
    const newCompleted = isFirst ? pageCompleted : this.data.completedCount + pageCompleted
 
    // 一次性 setData,原子性写入所有派生值
    this.setData({
      ['gift.list[' + (page - 1) + ']']: arr,
      'gift.loaded': true,
      exchangedCount: newExchanged,
      completedCount: newCompleted,
      totalCount: newExchanged + newCompleted,
    })
  })
}
正解:从原始数据源算,而不是从 this.data 算

把派生值的计算挪到 setData 之前,基于你刚拿到的原始数组(arr)完成所有统计,然后把列表和派生值一并写入同一个 setData。这样既避免了时序问题,又减少了一次额外的 setData 调用。

什么时候 _updateCounts() 还能用

如果你修改的是列表里已有的单条记录(比如从详情页返回后改某一项的状态),此时 this.data 里其他记录都是稳定的,可以在单项 setData 之后再调统计方法——但仍然要确认那次单项写入对 this.data 的影响是否已完成。最安全的做法还是把修改和重新统计合并为一次 setData

一句话外卖

在 Omix/OBAA 页面里,setData 后 this.data 不是真相来源——应该从你刚构建的数据对象算派生值,然后一次原子写入。