setData 为什么会慢?如何优化小程序的数据更新?
从 Shadow 树规模、更新数据量、调用频率和后台页面四个方面说明 setData 的性能影响因素与官方优化建议。
参考答案
简答
- 官方列出的两个主要因素是:调用
setData的组件 Shadow 树节点总量,以及更新的数据量,前者影响更大。 - 合理拆分组件、控制单个 Shadow 树的大小,是最有效的优化手段。
- 只传变化的字段,可以用数据路径(如
'list[3].done')更新局部,不要this.setData(this.data)。 data只放渲染用到的数据,渲染无关的数据挂在实例的其他字段上。- 合并连续调用、避免高频调用,页面进入后台后停止更新。
为什么会慢
setData 把逻辑层的数据序列化后发送给视图层,视图层再运行数据更新算法、重新渲染。官方文档指出,这个算法的耗时和 Shadow 树中的节点总量、节点复杂程度显著正相关;更新的数据量则影响数据拷贝和通信耗时。
调用过于频繁(毫秒级)时,逻辑层 JS 线程一直忙碌,无法及时响应用户事件和页面切换;视图层也忙于处理更新,滑动卡顿、操作反馈延迟。
拆分组件,缩小 Shadow 树
页面节点很多时,把频繁更新的部分封装成独立的自定义组件,在组件内部调用 setData。这样每次更新只在小组件的 Shadow 树上运行算法。秒杀倒计时就是典型场景:倒计时放进单独组件,不要在整个页面上每秒更新。
官方还建议对这类区域使用 CSS contain 属性,限制布局、样式和绘制的计算范围。
只传变化的数据
JavaScript
Page({
data: { list: [] },
toggle(e) {
const index = e.currentTarget.dataset.index;
// 只更新一条记录的一个字段,不重新传整个 list
this.setData({ [`list[${index}].done`]: !this.data.list[index].done });
},
});
长列表应分页加载,加载更多时只追加新的条目路径。
data 只放渲染数据
data 中的字段每次更新都会触发渲染流程和跨层通信,因此:
- WXML 中用到的数据放在
data; - 与渲染间接相关的数据可以设为纯数据字段,用
observers监听变化; - 用户 ID、计时器句柄等与渲染无关的数据挂到
this.userData之类的普通字段上,不要用data在方法之间共享状态。
控制频率和后台更新
- 只在界面需要变化时调用,把同一轮里的多次
setData合并为一次。 - 不要在每次页面滚动回调里调用
setData,可以节流或改用 WXS、IntersectionObserver 等方式。 - 小程序逻辑层是单线程,后台页面的更新会抢占前台页面的资源,用户也看不到。页面隐藏后应暂停倒计时等更新,
onShow时再恢复。
如何定位
组件的 setUpdatePerformanceListener 接口可以获取每次更新的耗时统计,用来找到更新慢的组件。再结合开发者工具的性能面板和真机测试确认结果。
常见追问
setData是同步还是异步?逻辑层的this.data会同步更新,视图层渲染是异步的,需要在渲染完成后执行的逻辑放进setData的回调。- Taro、uni-app 还需要关注 setData 吗?需要。框架最终仍通过 setData 更新视图,组件拆分和数据量由业务代码决定,原理见《Taro 和 uni-app 如何把代码转换成小程序?编译时和运行时有什么区别?》。