小程序性能优化:从启动到 setData
你打开小程序,首页要转两秒才出来;往下滑到第 200 条数据时手指一顿一顿;点一个按钮,整页都重绘了。你以为是手机差,换成旗舰机也一样。小程序性能问题几乎都不是玄学,成因很具体:包太大导致下载解压慢、setData 传了不该传的东西、长列表一次性渲染全部节点。每一条都有明确做法可以量化改善。
一、先分清启动性能与运行性能
这是两类不同的优化对象。启动性能关心「从点击图标到首屏可用」,决定性因素是包体积和初始化逻辑量;运行性能关心「页面卡不卡」,取决于渲染数据量、频次和长列表策略。
优化前先量一下。开发者工具里有体验评分和性能面板,能看到启动耗时、首次渲染时间、setData 的调用次数与数据大小。没有数据的优化都是猜,给自己定两个指标:首屏可见时间、滑动掉帧情况,每次改动后复测。
二、主包体积:最有效的杠杆
主包体积有硬性上限,直接影响冷启动的下载与解压时间。超了就必须拆分包,即使没超,能压小也值得。
用分包承载非首屏内容
分包后主包只放首页与公共依赖,其余页面按业务拆到子包,用户进入时才下载。拆分标准很简单:首屏路径上不需要的页面全部移出去。子包之间不能互相引用,公共模块要么提到主包,要么复制一份;确实有跨包共享代码时,可以配合分包异步化,做成需要时才加载的形式。别忘了预下载——首页渲染完成后主动触发子包预下载,用户点进详情时子包已在本地。
体积控制的具体手段
- 图片全部挪到 CDN,包里不留大图,首屏之外的用远程地址懒加载。
- 把多页共用的工具函数、配置、组件抽成公共模块,避免每个子包各打一份。
- 删掉没用的代码。包里往往躺着废弃页面、调试假数据、整个引进来只用了一个函数的库。
- 按需注入:只注入当前页面需要的代码与自定义组件,别把所有页面组件都塞进启动流程。
初始渲染缓存
它让页面在逻辑层还没准备好时先展示一份上次的快照,用户看到内容的时间明显提前,适合不依赖实时数据的静态页面。注意数据可能过期,展示前留一个短暂的加载态。
三、setData 的三个原则
setData 是这里最贵的操作,它要把数据序列化后从逻辑层传到视图层,数据越大、次数越多、层级越深开销越高。三个原则能解决大部分卡顿。
量少
只传视图层真正要用的数据。典型错误是把接口原始报文整包 setData,其中大半字段根本不显示。
频次低
合并短时间内的多次更新。滚动监听里每帧都 setData 是最常见的灾难来源,改成节流或只在临界值变化时更新。表单输入同理。
路径精准
用数据路径直接更新单个字段,而不是整个对象重新赋值。
// 差:整个列表重传,一千条就是一千条的开销
this.setData({ list: newList });
// 好:只更新第 3 条的 status
this.setData({ 'list[3].status': 1 });
// 好:只更新对象的一个字段
this.setData({ 'form.title': value });
还有一个隐藏成本:不要把函数、节点、不可序列化的对象放进 data。纯数据留在 data,逻辑留在页面方法里。
四、长列表怎么渲染
一次性渲染上千条节点,怎么优化 setData 都救不回来。三条路按场景选。
虚拟列表
只渲染视口内可见的若干条,滚动时动态替换数据,节点数量恒定,滚动始终流畅。代价是实现复杂,要处理行高计算、滚动位置同步、快速滑动的白屏。
分页加载
一次只渲染几十条,滚到底部再加一批。实现简单,但一直往下拉时节点总数还是会膨胀,通常配合「超出一定条数后丢弃远端数据」来限制。
骨架屏与占位
数据没到时先渲染结构一致的占位块,给等待一个视觉交代。它不减少真实耗时,但改善主观感受,骨架要贴近真实布局,尺寸跳变会让人觉得更慢。
| 方案 | 适用场景 | 注意点 |
|---|---|---|
| 虚拟列表 | 数据量大、滚动频繁、行高可预测 | 实现复杂,注意快速滑动白屏 |
| 分页加载 | 数据中等、用户很少滑到很深 | 限制总节点数,及时释放旧数据 |
| 骨架屏 | 所有需要等待网络的首屏 | 尺寸贴近真实内容,避免跳动 |
五、图片是最容易漏掉的成本
一个页面的内存与渲染压力,很大比例来自图片。四个动作立刻见效。
- 请求尺寸合适的图:服务端或 CDN 按展示尺寸裁切,别把 2000px 缩到 200px 显示。
- 换格式:支持的话优先用压缩率更好的新格式,同时保留回退。
- 懒加载:视口之外的图延迟请求,滚动到附近再加载。
- 设固定尺寸:给容器明确宽高,避免图片加载后把页面顶得跳动。
六、网络请求与缓存
首屏常被串行请求拖慢:先拿用户信息,再用它拿列表,再拿列表拿详情。把能并行的并行,把能提前的提前,首屏用一次批量接口返回必要字段。
两条路:网络层缓存头,和本地存储。把不常变的配置、字典数据存本地并带过期时间。
const cached = wx.getStorageSync('config');
if (cached && Date.now() - cached.time < 3600e3) {
this.applyConfig(cached.data);
}
request('/api/config').then(res => {
wx.setStorageSync('config', { data: res, time: Date.now() });
this.applyConfig(res);
});
控制关键的几个并行,其余延后。
七、体验评分里真正影响用户的几项
工具会给很多条建议,有的对真实感受影响巨大,有的只是锦上添花。按优先级排:
- 首屏可见时间:最核心。点图标后多久看到内容,直接决定用户会不会退出。
- 交互响应:按钮点下去有没有即时反馈,没有反馈的 500 毫秒在感知上像卡死。
- 滑动流畅度:长列表卡顿是最大的差评来源,优先处理。
- 请求失败要有提示,避免布局抖动:空白页与内容跳动都会让人误操作。
八、优化项与收益、成本对照
| 优化项 | 收益 | 实施成本 |
|---|---|---|
| 图片上 CDN 并裁切尺寸 | 高,包体积与内存双降 | 低 |
| 拆分分包 | 高,主包明显变小 | 中,需调整目录与依赖 |
| 分包预下载 | 中高,消除二次跳转等待 | 低 |
| 按需注入 | 中,启动流程变轻 | 低 |
| 初始渲染缓存 | 高,首屏感知大幅提前 | 中,需处理数据时效 |
| setData 精简与合并 | 高,直接改善卡顿 | 中,需逐页排查 |
| 虚拟列表 | 高,长列表体验质变 | 高 |
| 请求并行与批量接口 | 中高,首屏时间缩短 | 中,需后端配合 |
| 本地缓存字典数据 | 中,重复进页面秒开 | 低 |
先把「大图、大包、大 setData」砍掉,多数项目的体验就从难用变成可用了。虚拟列表这类重投入方案,等前三条做完再看数据决定。






