一 前言
本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
为什么小程序如此受欢迎?
随着移动互联网发展,各大主流的 App 的很多业务页面,都需要有动态化发版的能力,这时小程序的优势就体现出来了,首先小程序无需安装和卸载,更少的占用内存,并且实现了跨端兼容,开发者无需在安卓或者 iOS 端开发两套代码,这无疑降低了开发成本,而且小程序更受到广大前端开发者的青睐,随着 taro 等框架的成熟,开发者可以完全做到像开发 web 应用一样开发小程序。
setData 优化迫在眉睫 随着小程序的发展,各种各样的小程序百花齐放,截止 2022 年末,互联网小程序总数超过 780 万,DAU更是突破了 8 亿。小程序承载了越来越多的功能,这就促使了小程序的模块越来越复杂。这个时候,更新视图就会牵连更多的业务模块的联动更新,如果小程序开发者不做优化而是肆意的使用 setData,就会让应用更卡顿,渲染更耗时,直接影响了用户体验。所以 setData 优化是小程序优化重要的组成部分。
要是彻底弄明白 setData 影响性能的原因,就要从小程序的架构设计说起。
二 双线程架构设计
2.1 小程序双线程架构设计
小程序采用双线程架构,分为逻辑层和渲染层。首先就是 Native 打开一个 WebView 页面,渲染层加载 WXML 和 WXSS 编译后的文件,同时逻辑层用于逻辑处理,比如触发网络请求、setData 更新等等。接下来是请求资源,请求到数据之后,数据先通过逻辑层传递给 Native,然后通过 Native 把数据传递给渲染层 WebView,再进行渲染。
在小程序中,触发的事件首先需要传递给 Native,再传递给逻辑层,逻辑层处理事件,再把处理好的数据传递给 Native,最后 Native 传递给渲染层,由渲染层负责渲染。
2.2 小程序更新原理
上面小程序的双线程架构,setData 是驱动小程序视图更新的核心方法,通过上面双线程架构可知,setData 过程中,需要把更新的数据,先传递给 Native 层,然后 Native 层再传递给 webView 层面。
数据这么一来一回需要实现 Native <-> JS 引擎双线程通信,并且数据在通信过程中,需要序列化和反序列化,那么在此期间就会产生大量的通信成本。这就是 setData 消耗性能,性能瓶颈的原因。
明白了 setData 的性能瓶颈之后,来看一下如何优化 setData 呢?
三 setData 优化
对于 setData 的优化,重点是以下三个方面:
- 控制 setData 的数量(频率)。
- 控制 setData 的量。
- 合理运用 setData 。
下面我们对这三个方向分别展开讨论。
3.1 减少 setData 的数
首先第一点就是控制 setData 的次数, 每次 setData 都会触发逻辑层虚拟 DOM 树的遍历和更新,也可能会导致触发一次完整的页面渲染流程,其中就包括了序列化,通信,反序列化的过程。过于频繁(毫秒级)的调用 setData,会造成严重的影响,如下:
- 逻辑层 JS 线程持续繁忙,无法正常响应用户操作的事件,也无法正常完成页面切换;
- 视图层 JS 线程持续处于忙碌状态,逻辑层 -> 视图层通信耗时上升,视图层收到消息的延时较高,渲染出现明显延迟;
- 视图层无法及时响应用户操作,用户滑动页面时感到明显卡顿,操作反馈延迟,用户操作事件无法及时传递到逻辑层,逻辑层亦无法及时将操作处理结果及时传递到视图层。
因此,开发者在调用 setData 是,应该做如下处理:
1.仅在需要进行页面内容更新时调用 setData。
有一些场景下,我们没有必要把所有的数据,都用 setData, 一些数据可以直接通过 this 来保存,setData 只更新有关视图的数据。
比如有一个状态叫做 isFlag, 这个状态只是记录状态,并不是用于渲染。那么没必要用 setData。
不推荐:
js
this.setData({
isFlag:true
})
推荐:
js
this.isFlag = true
2.合并 setData:
把多个 setData 可以合并成一个 setData ,避免同一个上下文中,多个 setData。
不推荐:
js
this.setData({
isFlag:true
})
this.setData({
number:1
})
推荐:
js
this.setData({
isFlag:true,
number:1
})
3.避免以过高的频率持续调用 setData,例如毫秒级的倒计时,scroll里面使用 setData
不推荐:
js
// ❌
onScoll(){
this.setData({
xxx:...
})
}
// ❌
setTimeout(()=>{
this.setData({
xxx:...
})
},10)
如果必须在 scroll 事件中使用 setData ,那么推荐使用函数防抖(debounce),或者函数节流(throttle);
js
onLoad(){
this.onScroll = debounce(this.onScroll.bind(this),200)
}
onScroll(){}
3.2 减少 setDate 的量
setData 只用来进行渲染相关的数据更新。用 setData 的方式更新渲染无关的字段,会触发额外的渲染流程,或者增加传输的数据量,影响渲染耗时。
1.data 里面仅存放和渲染有关的数据。
js
this. ({
data1:...
data2:...
})
html
<view>{{ data1 }}</view>
如上有两个数据 data1 和 data2, 但是只有 data1 视图需要,那么 setData 改变 data2 就是多余的。
2.组件间的通信,可以通过状态管理工具,或者 eventbus
比如有一个数据 a, 想把 a 传递到子组件中,那么通常的方案是 a 作为 props 传递给子组件,如果想要改变 a 的值,那么需要 setData 更新 a 的值。
如果是普通的组件,如上的传递方式是没问题的,但是对于一些复杂的场景,比如传递的数据巨大,这个时候就可以考虑用状态管理工具,或者 eventbus 的方式。
如下就是通过 eventBus 实现的组件通信。
js
import { BusService } from './eventBus'
Component({
lifetimes:{
attached(){
BusService.on('message',(value)=>{ /* 事件绑定 */
/* 更新数据 */
this.setData({...})
})
},
detached(){
BusService.off('message') /* 解绑事件 */
}
},
})
js
Component({
methods:{
emitEvent(){
BusService.emit('message','hello,world')
}
}
})
3.控制 setData 数据更新范围。
对于列表或者是大对象的数据结构,如果是列表某一项的数据变化,或者是对象的某一属性发生变化,可以控制 setData 数据更新范围,让更新的数据变得最小。
如下:
js
handleListChange(index,value){
this.setData({
`sourceList[${index}]`:value
})
}
3.3 合理运用 setData
如上就是通过 setData 的频率和数量大小,来优化 setData 性能,除此之外,还需要一些业务系统性的优化 setData 的手段。
1.数据源分层
对于复杂的业务场景(复杂的列表,或者复杂的模块场景),服务端数据肯定包含了很多信息,这些数据有的是用于渲染的,有的是用于逻辑处理的,还有的是用于处理埋点和广告的,如果把所有的数据都通过 setData 传递,庞大的数据传输可能会阻塞页面的渲染展示。
这个时候,我们可以把数据分层处理,分成用于纯渲染的数据,逻辑数据,埋点数据等。
伪代码如下所示:
js
// 处理服务端返回的数据
handleRequestData(data){
/* 处理业务数据 */
const { renderData,serviceData,reportData } = this.handleBusinessData(data)
/* 只有渲染需要的数据才更新 */
this.setData({
renderData
})
/* 保存逻辑数据,和上报数据 */
this.serviceData = serviceData
this.reportData = reportData
}
2.渲染分片
还有一个场景就是页面确实有很多模块需要渲染,这个时候在所难免要用 setData 更新大量的数据,如果把这些渲染的数据一次性更新完,也会占用一定的时间;针对这个场景就可以使用渲染分片的概念。就是优先渲染第一屏模块,其他模块用 setTimeout 分片渲染,这样可以缓解一次 setData 造成的压力。
js
Page({
data:{
templateList:[],
},
async onLoad(){
/* 请求初始化参数 */
const { moduleList } = await requestData()
/* 渲染分组,每五个模版分成一组 */
const templateList = this.group(moduleList,5)
this.updateTemplateData(templateList)
},
/* 将渲染模版进行分组 */
group(array, subGroupLength) {
let index = 0;
const newArray = [];
while (index < array.length) {
newArray.push(array.slice(index, (index += subGroupLength)));
}
return newArray;
},
/* 更新模版数据 */
updateTemplateData(array, index = 0) {
if (Array.isArray(array)) {
this.setData(
{
[`templateList[${index}]`]: array[index],
},
() => {
if (index + 1 < array.length) {
setTimeout(()=>{
this.updateTemplateData(array, index + 1);
},100)
}
}
);
}
},
})
3.业务场景定制
针对一些特定的业务场景,需要制定符合当前业务场景的技术方案。这个可能要求开发者有一定的架构设计能力。这里就不具体介绍了。
四 总结
本文讲了小程序的 setData 的一些优化方案,希望能给读过文章的读者在小程序 setData 优化方向,提供一个思路。
最好,希望感觉有帮助的朋友能够 点赞 + 收藏,关注我,持续分享前端