微信开发者

用好setData 性能不再卡

随着小程序生态的蓬勃发展,多样化的用户需求推动更丰富的小程序功能。小程序承载更多的能力,伴随着更高的性能要求。无论是启动性能,还是运行性能,都会对用户体验产生影响,最终影响业务发展。

开发者可以通过 按需注入、分包异步 等方式来优化启动性能。针对运行性能优化,合理使用 setData 是重要一环,离不开其中 2 个重要方式:

  • 减小每次 setData 的数据体积

  • 控制调用 setData 频率

Image

优化diff算法 减小数据体积 

setData 建议只用来进行渲染相关的数据更新。调用 setData 更新与渲染无关的字段,触发额外的渲染流程,或者增加传输的数据量,影响渲染耗时。
减小数据体积主要有 2 个方向:
  1. 通过 setData 之前 diff 来减小数据体积
  2. 将不需要渲染到 wxml 的数据从 data 转移到其他地方存储
其中方向 1 能够通过递归方式深度遍历整个对象,从框架层面上对数据体积进行全面优化。
if (currentType != ARRAYTYPE && currentType != OBJECTTYPE) {  // 基本类型直接对比  if (currentValue !== previous[key]) {    setResult(result, concatPathAndKey(path, key), currentValue);  }} else if (currentType == ARRAYTYPE) {  // 处理数组  if (preType != ARRAYTYPE) {    // 类型不符直接使用新数据    setResult(result, concatPathAndKey(path, key), currentValue);  } else {    // 数组长度不符直接使用新数据    if (currentValue.length < preValue.length) {      setResult(result, concatPathAndKey(path, key), currentValue);    } else {      // 其他情况递归每个数组元素      currentValue.forEach((item, index) => {        _diff(          item,          preValue[index],          concatPathAndKey(path, key) + '[' + index + ']',          result,        );      });    }  }} else if (currentType == OBJECTTYPE) {  if (    preType != OBJECTTYPE ||    Object.keys(currentValue).length < Object.keys(preValue).length  ) {    // 对象同样先对比类型和长度,不符直接使用新对象    setResult(result, concatPathAndKey(path, key), currentValue);  } else {    // 否则递归对象中的每个元素    for (const subKey in currentValue) {      const realPath =            concatPathAndKey(path, key) +            (subKey.includes('.') ? `["${subKey}"]` : `.${subKey}`);      _diff(currentValue[subKey], preValue[subKey], realPath, result);    }  }
以购物车页面为例,将 setData 替换成 updateData。对比替换前 80KB 的数据量,经过 diff 优化的数据量减少至 0.8KB~20KB,数据减少比例最高达 100倍。数据体积减小使得 setData 消耗总时间优化 22%,渲染层总时间优化 39%。
然而,data 嵌套多层数组和对象,导致 diff 算法遍历过程消耗大量时间。如果加上 diff 耗时,总时间仅优化 13.6%。因此优化 diff 算法非常必要,也非常简单。在 for in 循环中增加一个判断条件,跳过部分 object 和 array,避免遍历所有对象,能够减少 diff 耗时 16%。
if (JSON.stringify(currentValue) === JSON.stringify(previous[key])) {  continue;}
Image

合并setData 控制调用频率

由于每次 setData 都会触发逻辑层虚拟 DOM 树的遍历和更新,可能导致触发完整的页面渲染流程。因此,建议开发者将一个同步时间片里面的 setData 合并为一个,在下一个时间片执行 setData,减少对象遍历以及完整渲染的工作。

performUpdate() {    // 已经执行过不再执行    if (Object.keys(this.dataMap).length === 0) {      return;    }
// 一个时间片内执行一次,执行后清除数据 this.oriSetData(this.dataMap, () => { // 依次执行callback this.callbackQueue.forEach(callback => { if (typeof callback === 'function') { callback(); } });
this.callbackQueue = []; }); this.dataMap = {}; }
enqueueUpdate() { this.oriSetData = this.setData; this.setData = (data, callback) => { // 保存并合并data this.dataMap = Object.assign(this.dataMap, data); // 保存callback this.callbackQueue.push(callback); wx.nextTick(() => { this.performUpdate(); }); };  }
Image

合理使用 setData 能够有效优化小程序运行性能,其中通过 diff 算法减小数据体积以及合并 setData 来控制调用频率 2 种方法减少 setData 耗时 22%,成功优化小程序性能体验。更多性能优化干货内容,欢迎点击 性能优化指南 了解。

如有其他小程序应用相关的问题,可在 微信开放社区小程序交流专区 发帖互动,技术专员将为大家解答及进行深度交流。
原创|腾讯前端开发工程师 shiyanzhang
Image