小程序渲染为什么会卡
和网页一样,小程序也是「逻辑层算数据、渲染层画界面」。卡的根源通常不是手机不够强,而是一次塞给渲染层的东西太多、或者 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 通信、还是渲染层重绘、还是长任务卡住主线程。量出来了,优化才有的放矢。
常见的渲染卡顿坑
| 坑 | 后果 | 应对 |
|---|---|---|
| 整包 setData | 通信卡顿 | 只传变化字段 |
| 长列表全量渲染 | 滑动掉帧 | 分页+按需渲染 |
| 渲染帧里做重计算 | 整页卡住 | 提前算好再 setData |
还有个容易被忽略的:不必要的复杂选择器和深层嵌套样式,也会让渲染层在重排时多花时间。样式写简单点,渲染也轻松。
渲染性能优化清单
- setData 只传变化的字段,控制频率。
- 长列表分页加载,图片懒加载。
- 组件拆细,纯展示块避免无谓重渲染。
- 重计算提前做完,别压在渲染帧里。
- 用真机性能面板量,别凭手感。
优化完一定要用同一台真机、同一套路径复测,确认卡顿真的消了,而不是感觉变快了。性能是测出来的,不是感觉出来的。
渲染性能不是玄学,把每一帧要画的东西减到最少就对了。