小程序渲染为什么会卡

和网页一样,小程序也是「逻辑层算数据、渲染层画界面」。卡的根源通常不是手机不够强,而是一次塞给渲染层的东西太多、或者 setData 调得太频繁。把这两点压住,大多数卡顿就解决了。

排查渲染卡顿有个顺序:先看是不是 setData 数据量太大、太频繁,再看是不是一次渲染了太长的列表,最后才轮到样式和组件本身。别一上来就怀疑手机。

卡顿十有八九是「渲染了不该这一帧渲染的东西」。

还有一个常见误区:以为加了骨架屏、加了加载态就万事大吉。骨架屏解决的是「等的时候难看」,没解决「为什么这么慢」。真正的卡顿还得靠渲染优化压下去。

本文核心知识点
  • setData只传变了的字段,别整包刷。
  • 长列表分页加载、按需渲染。
  • 自定义组件拆分粒度,减少重渲染。
  • 分层逻辑层别做重计算拖慢渲染。
小程序渲染性能示意:蓝调渐变背景上分层渲染管线与流畅数据流的扁平科技插画
把数据变更压到最少、把列表按需渲染,渲染自然就顺了。

setData:最常见的性能杀手

setData 是逻辑层到渲染层的桥梁,每次调用都有一次跨层通信。很多卡顿就来自频繁、又把整个对象塞进去的 setData。

做法影响
整包 data 一起刷通信量大,渲染重
高频逐帧 setData主线程被占住
只传变化的字段通信最小,最顺
// 不好:把整个对象重新塞一遍
this.setData({ list: wholeList });

// 好:只更新变了的那一项
this.setData({ ["list[" + idx + "].price"]: newPrice });

除了数据量,setData 的时机也有讲究。不要在一个事件里连续 setData 好几次,可以先把数据在本地算好,最后一次性 setData。多次跨层通信攒成一次,开销小很多。

长列表:别一次渲染全部

商品流、消息列表动辄几百条,一次性全渲染出来,光是画节点就把一帧占满了。正解是分页加载 + 按需渲染:先出一屏,滑到底再补下一批。

// 触底时再加载下一页
onReachBottom() {
  this.loadNextPage();
}

列表项里的图片也要懒加载,别一上来把所有图都请求一遍。

如果列表特别长、又要快速定位,用平台提供的长列表组件会比自己写 scroll-view 靠谱。它内部做了节点复用,看不见的项不渲染,滑起来自然流畅。

自定义组件:让渲染更轻

组件拆分得当,数据变了只重渲染受影响的那一小块。反之,一个超大组件,随便改个字段都整页重画。

  • 把独立的小块抽成组件,边界清晰。
  • 组件内部状态别全甩给页面管。
  • 纯展示的组件,数据没变就别让它重渲染。

组件的属性也别传太大的对象。每次属性变化都可能触发重渲染,把一个庞然大物当属性传进去,等于把组件拖回了大组件的老路。

逻辑层与渲染层的协作

渲染层在画的时候,逻辑层别同时塞一堆重计算。复杂排序、大循环能挪到数据回来时就算好,就别卡在渲染那一帧里。

// 数据回来先算好要展示的字段,渲染层直接用
const viewList = rawList.map(formatItem);
this.setData({ list: viewList });

原则是:渲染层拿到的应该是「能直接画」的数据,而不是还要当场算一遍。

调试时打开性能面板,看每一帧的耗时花在哪:是 setData 通信、还是渲染层重绘、还是长任务卡住主线程。量出来了,优化才有的放矢。