小程序 setData 学问多

为何不能频繁 setData

先科普下 setData 作的事情:html

在数据传输时,逻辑层会执行一次 JSON.stringify 来去除掉 setData 数据中不可传输的部分,以后将数据发送给视图层。同时,逻辑层还会将 setData 所设置的数据字段与 data 合并,使开发者能够用 this.data 读取到变动后的数据。小程序

所以频繁调用,视图会一直更新,阻塞用户交互,引起性能问题。微信小程序

但频繁调用是常见开发场景,能不能频繁调用的同时,视图延迟更新呢?数组

参考 Vue,咱们能知道,Vue 每次赋值操做并不会直接更新视图,而是缓存到一个数据更新队列中,异步更新,再触发渲染,此时屡次赋值,也只会渲染一次。浏览器

let newState = null;
let timeout = null;
const asyncSetData = ({
  vm,
  newData,
}) => {
  newState = {
    ...newState,
    ...newData,
  };
  clearTimeout(timeout);
  timeout = setTimeout(() => {
    vm.setData({
      ...newState,
    });
    newState = null
  }, 0);
};
复制代码

因为异步代码会在同步代码以后执行,所以,当你屡次使用 asyncSetData 设置 newState 时,newState 都会被缓存起来,并异步 setData 一次缓存

但同时,这个方案也会带来一个新的问题,同步代码会阻塞页面的渲染微信

同步代码会阻塞页面的渲染的问题其实在浏览器中也存在,但在小程序中,因为是逻辑、视图双线程架构,所以逻辑并不会阻塞视图渲染,这是小程序的优势,但在这套方案将会丢失这个优势。数据结构

鱼与熊掌不可兼得也!架构

对于信息流页面,数据过多怎么办

单次设置的数据不能超过 1024kB,请尽可能避免一次设置过多的数据异步

一般,咱们拉取到分页的数据 newList,添加到数组里,通常是这么写:

this.setData({
  list: this.data.list.concat(newList)
})
复制代码

随着分页次数的增长,list 会逐渐增大,当超过 1024 kb 时,程序会报 exceed max data size 错误。

为了不这个问题,咱们能够直接修改 list 的某项数据,而不是对整个 list 从新赋值:

let length = this.data.list.length;
let newData = newList.reduce((acc, v, i)=>{
  acc[`list[${length+i}]`] = v;
  return acc;
}, {});
this.setData(newData);
复制代码

这看着彷佛还有点繁琐,为了简化操做,咱们能够把 list 的数据结构从一维数组改成二维数组:list = [newList, newList], 每次分页,能够直接将整个 newList 赋值到 list 做为一个子数组,此时赋值方式为:

let length = this.data.list.length;
this.setData({
  [`list[${length}]`]: newList
});
复制代码

同时,模板也须要相应改为二重循环:

<block wx:for="{{list}}" wx:for-item="listItem" wx:key="{{listItem}}">
  <child wx:for="{{listItem}}" wx:key="{{item}}"></child>
</block>
复制代码

下拉加载,让咱们一晚上回到解放前

信息流产品,总避免不了要作下拉加载。

下拉加载的数据,须要插到 list 的最前面,因此咱们应该这样作:

this.setData({
  `list[-1]`: newList
})
复制代码

哦不,对不起,上面是错的,应该是下面这样:

this.setData({
  list: this.data.list.unshift(newList)
});
复制代码

这下好,又是一次性修改整个数组,一晚上回到解放前......

为了解决这个问题,这里须要一点奇淫巧技:

  • 为下拉加载维护一个单独的二维数组 pullDownList
  • 在渲染时,用 wxs 将 pullDownList reverse 一下

此时,当下拉加载时,即可以只修改数组的某个子项:

let length = this.data.pullDownList.length;
this.setData({
  [`pullDownList[${length}]`]: newList
});
复制代码

关键在于渲染时候的反向渲染

<wxs module="utils">
function reverseArr(arr) {
  return arr.reverse()
}
module.exports = {
  reverseArr: reverseArr
}
</wxs>
<block wx:for="{{utils.reverseArr(pullDownList)}}" wx:for-item="listItem" wx:key="{{listItem}}">
  <child wx:for="{{listItem}}" wx:key="{{item}}"></child>
</block>
复制代码

问题解决!

参考资料

相关文章
相关标签/搜索