前言

setData 是微信小程序开发中使用最频繁、也是最容易引发性能问题的接口。

setData 的流程

  1. 逻辑层虚拟 DOM 树的遍历和更新,触发组件生命周期和 observer 等
  2. 将 data 从逻辑层传输到视图层
  3. 视图层虚拟 DOM 树的更新、真实 DOM 元素的更新并触发页面渲染更新

数据通信

由于小程序的逻辑层和视图层是两个独立的运行环境、分属不同的线程或进程,不能直接进行数据共享,需要进行数据的序列化、跨线程/进程的数据传输、数据的反序列化,因此 **数据传输过程是异步的、非实时的**。
数据传输的耗时与数据量的大小正相关,如果对端线程处于繁忙状态,数据会在消息队列中等待。

使用优化

data 应只包括渲染相关的数据

setData() 应只用来进行渲染相关的数据更新。用 setData 的方式更新渲染无关的字段,会触发额外的渲染流程,或者增加传输的数据量,影响渲染耗时。页面或组件渲染无关的数据,应挂在非 data 的字段下,如 this.userData = {userId: 'xxx'};

控制 setData() 的频率

每次 setData() 都会触发逻辑层虚拟 DOM 树的遍历和更新,也可能会导致触发一次完整的页面渲染流程。过于频繁(毫秒级)的调用 setData(),会导致以下后果:

  • 逻辑层 JS 线程持续繁忙,无法正常响应用户操作的事件,也无法正常完成页面切换
  • 视图层 JS 线程持续处于忙碌状态,逻辑层 -> 视图层通信耗时上升,视图层收到消息的延时较高,渲染出现明显延迟
  • 视图层无法及时响应用户操作,用户滑动页面时感到明显卡顿,操作反馈延迟,用户操作事件无法及时传递到逻辑层,逻辑层亦无法及时将操作处理结果及时传递到视图层

因遵守以下规则:

  • ✅ 仅在需要进行页面内容更新时调用 setData()
  • ✅ 对连续的 setData() 调用尽可能的进行合并
  • ❌ 避免不必要的 setData
  • ❌ 避免以过高的频率持续调用 setData(),例如毫秒级的倒计时
  • ❌ 避免在 onPageScroll 回调中每次都调用 setData()

选择合适的 setData() 范围

组件的 setData() 只会引起当前组件和子组件的更新,可以降低虚拟 DOM 更新时的计算开销。对于需要频繁更新的页面元素(例如:秒杀倒计时),可以封装为独立的组件,在组件内进行 setData() 操作。

setData() 应只传发生变化的数据

setData() 的数据量会影响数据拷贝和数据通讯的耗时,增加页面更新的开销,造成页面更新延迟。应该以数据路径形式改变数组中的某一项或对象的某个属性,而不是每次都更新整个对象或数组。

控制 后台态 页面的 setData()

由于小程序逻辑层是单线程运行的,后台态页面去 setData() 也会抢占前台页面的运行资源,且后台态页面的的渲染用户是无法感知的,会产生浪费。在某些平台上,小程序渲染层各 WebView 也是共享同一个线程,后台页面的渲染和逻辑执行也会导致前台页面的卡顿。