「小程序进阶」setData 优化实践指南

一 前言

本文为稀土掘金技术社区首发签约文章,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 优化方向,提供一个思路。

最好,希望感觉有帮助的朋友能够 点赞 + 收藏,关注我,持续分享前端

参考文献

相关推荐
guokanglun2 分钟前
Vue.js动态组件使用
前端·javascript·vue.js
-seventy-15 分钟前
Ajax 与 Vue 框架应用点——随笔谈
前端
我认不到你31 分钟前
antd proFromSelect 懒加载+模糊查询
前端·javascript·react.js·typescript
集成显卡33 分钟前
axios平替!用浏览器自带的fetch处理AJAX(兼容表单/JSON/文件上传)
前端·ajax·json
焚琴煮鹤的熊熊野火42 分钟前
前端垂直居中的多种实现方式及应用分析
前端
我是苏苏1 小时前
C# Main函数中调用异步方法
前端·javascript·c#
晓风伴月1 小时前
uniapp: IOS微信小程序输入框部分被软键盘遮挡问题
微信小程序·小程序·uni-app
转角羊儿1 小时前
uni-app文章列表制作⑧
前端·javascript·uni-app
大G哥1 小时前
python 数据类型----可变数据类型
linux·服务器·开发语言·前端·python
hong_zc2 小时前
初始 html
前端·html