Omix setData 之后派生值算出来是 0,根因是 this.data 还没同步
在一个微信小程序的积分兑换页里,我有个方法负责拉取礼品列表,拉完后立刻调 _updateCounts() 统计已兑换和已完成数量,最后渲染到页面顶部。逻辑看上去无懈可击:拉数据、存数据、算数据,顺序执行。但上线后发现数量栏目始终显示 0,而列表本身渲染完全正常,数据明明在那里。
现象
setData把礼品列表写入gift.list[0],WXML 里列表渲染正确,每条记录都在_updateCounts()在setData调用后立即执行,读this.data.gift.list遍历统计- 打印
this.data.gift.list,拿到的是更新前的旧数组,length 为 0 - 统计结果
exchanged和completed均为 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 引入的这一层包装让两者语义产生了微妙的分叉。
在使用 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,
})
})
}把派生值的计算挪到 setData 之前,基于你刚拿到的原始数组(arr)完成所有统计,然后把列表和派生值一并写入同一个 setData。这样既避免了时序问题,又减少了一次额外的 setData 调用。
什么时候 _updateCounts() 还能用
如果你修改的是列表里已有的单条记录(比如从详情页返回后改某一项的状态),此时 this.data 里其他记录都是稳定的,可以在单项 setData 之后再调统计方法——但仍然要确认那次单项写入对 this.data 的影响是否已完成。最安全的做法还是把修改和重新统计合并为一次 setData。
一句话外卖
在 Omix/OBAA 页面里,setData 后 this.data 不是真相来源——应该从你刚构建的数据对象算派生值,然后一次原子写入。