题引:
今天在敲业务的时候,同事在旁边抓耳饶腮。我就问他怎么了,他说微信小程序的数据太大了,超出setData数据的最大上限,无法渲染。于是我就深入探讨了这个问题。
正文:
在我们使用原生小程序开发时,this.setData() 是我们用得最频繁的一个函数,主要是为了让逻辑层的数据变动与视图层进行通信。但是当我们的项目越来越大时,就会容易出现卡顿、渲染严重超时、以及更严重的无法渲染等问题。这个时候我们就需要对 this.setData() 进行更加深入的了解。
这里就不得不借助官网文档的一个相关描述。(所以官方文档很重要)
是的,如果学过React的同学,其实会发现小程序的数据修改方式和React是很像的。 小程序通过this.setData() 来修改,而React是通过 setState 。这里有三个点需要特别注重讲一下。
-
不能直接修改this.data:因为如果直接修改this.data的数据,小程序的逻辑层只会内部进行虚拟DOM和真实DOM进行对比修改,而不会去沟通视图层进行更新。只有使用 this.setData() 函数,逻辑层才会在修改自身后去沟通视图层进行更改。
- this.setData() 内部的逻辑
- 逻辑层虚拟 DOM 树的遍历和更新,触发组件生命周期和 observer 等;
- 将 data 从逻辑层传输到视图层;
- 视图层虚拟 DOM 树的更新、真实 DOM 元素的更新并触发页面渲染更新。
- this.setData() 内部的逻辑
-
单次数据不能超过1024KB(1MB):通过 this.setData() 函数进行修改赋值时,对象的总大小不能超过1MB,如果超过,小程序则会提示错误:
setData 数据传输长度为 xxxx KB,存在有性能问题!
因此,解决这个问题可以从两个角度出发:数据来源以及数据接受者。
- 数据来源(后端):可以跟后端人员进行协商,去除没有必要的属性,保留必要的属性;
- 数据接受者(前端):
- 自己进行数据清理和剔除,进而重组出一个嵌套层次比较简单的数据结构来保存数据对象(如map,二维数组等等);
- 避免毫秒级频繁调用 this.setData():对于过于庞大的数据对象,我们可能会通过分批渲染的形式来处理它,以至于我们会通过定时器或者循环来调用 this.setData() ,虽然达到了我们的业务目的,但这是不可取的,因此它会造成很大的性能问题(文章后面进行说明),而是尽可能进行合并调用;
-
key使用数据路径的形式修改:这是要讲的第三个点。我们在日常开发中,对于一个比较繁琐的数据修改,一般都是这样的
data:{ classList:{ classId:1, className:'一年级一班', uuid:'xxx', studentList:[ {stuName:'小李',stuId:'110'} ... ] } } //当我们对某个属性进行修改时,我们通常会有这样几种情况: //这是错误示范,是前面讲的第一个点,视图层无法追踪变化进行更改 this.data.classList.classId = 2 //1.赋值变量,重新赋值 const newList = Object.assign(classList); newList.classId = 2; this.setData({ classList:newList }) //2.构造变量,重新赋值 const newList1 = this.data.classList; newList1.classId = 2; this.setData({ classList:newList1 }) 这是我们以往的修改方式,但是现在可以使用key为数据路径的方式。 //推荐用法,只会修改单个属性,且视图层会变动更改 this.setData({ ['classList.classId']:2 }) 这样,我们可以编写更少的代码,又能高效的进行处理数据变动。
使用规范:
当然,除了以上三个点,其实 this.setData() 函数是有很多使用规范的,下面会统一进行总结。
-
控制setData的使用次数:每次setData都会触发逻辑层虚拟DOM树的遍历和更新,也可能会导致触发一次完整的页面渲染流程。过于频繁(毫秒级,如在循环体里调用this.setData())的调用 setData,会导致一下后果:
- 逻辑层JS线程持续繁忙,无法响应正常用户操作的事件(如常见的树形组件展开子节点数据无法展开),也无法正常完成页面的切换;
- 视图层JS现成持续处于忙碌状态,逻辑层 -> 视图层通信耗时上升,视图层收到信息的延迟变高,渲染出现明显的延迟;
- 视图层无法及时响应用户操作,用户体验感下降,操作反馈延迟,用户操作时间无法及时传递到逻辑层,逻辑层亦无法将操作结果及时传递给视图层;
简单举个例子:一个朋友给你发一条信息,你可以在1秒内回复他。但是当他一秒内给你发了十几百条,你就忙不过来了。上面的意思就是这个意思。
因此,我们在连续需要调用setData的时候尽可能进行合并。
-
data尽可能只包括与渲染有关的数据 :setData应只用来进行渲染相关的数据更新。用setData的方式来更新无关渲染的字段,会触发额外的渲染流程开销
- 与页面渲染无关的数据,应挂在非data的字段下。如this.userData = {userId:'xxx'},而不是在this.data字段定义;
-
setData应只传发生变化的数据而非整体:setData的数据量会影响数据拷贝和数据通讯的耗时,增加页面更新的开销,造成页面更新延迟。
- setData应只传入发生变化的字段;
- 建议以 数据路径 形式来改变数组的某一项或者对象的某个属性;
- 不要在setData中偷懒一次性传所有data:this.setData(this.data);
以上就是我总结的有关使用setData的心得,希望和大家共勉,有什么手写错误的望指正。开工去咯~~~