优化建议
运行原理
与传统的h5
应用不同,小程序的运行从架构上分为 webview
和 worker
两个部分。webview
负责渲染,worker
则负责存储 数据
和执行 业务逻辑
。
- 而
webview
和worker
之间的通信是异步
的。这意味着当我们调用setData
时,我们的数据
并不会立即得到渲染,而是需要从worker
异步传输到webview
。 - 数据传输时需要序列化为字符串,然后通过
evaluateJavascript
的方式传输。这意味着数据的大小会影响性能。
优化首屏
首屏
的定义有很多种,但我们接下来所讨论的首屏
是指:
业务上来看第一次有意义的渲染
。比如如果是列表页面,那就是列表渲染出来的时刻。
控制小程序资源包大小
当用户访问一个小程序时,支付宝客户端会首先从 cdn
上下载小程序资源包,因此资源包大小会影响小程序的启动性能。
优化建议:
- 及时删除没有用的图片资源(所有图片资源都会默认打包进去)
- 控制图片的大小,不要使用大图片(稍大点的图片建议走cdn)
- 及时清理无用的代码
将数据请求提前到 onLoad
小程序运行时,会先触发页面的 onLoad
生命周期函数,然后将页面的初始数据(Page
的 data
)从 worker
传递到 webview
进行一次初始渲染,等待页面初始渲染完成之后,才会从 webview
发出通知到 worker
,此时才会触发 onReady
生命周期函数。
在我们分析的一些案例中,有些小程序会在 onReady
中发出请求,这会延缓首屏的渲染。
优化建议:
- 将数据请求提前到
onLoad
中
控制首屏一次性渲染的节点数量
当业务请求回来后,通常会调用 setData
触发页面的重新渲染。内部的执行过程如下:
- 数据从
worker
传递到webview
webview
上根据传过来的数据构造虚拟dom
,并与之前做diff
(从根节点开始),然后渲染。
由于worker
与webview
通信时,数据需要序列化,然后到了webview
需要执行evaluateJavascript
,因此如果一次性传输很大的数据,会影响首屏渲染的性能。
另外,如果 webview
上构造的节点过多,层级嵌套太深(在我们分析的案例中,有的小程序列表页面会一次性渲染超过100个列表项,每个列表项又有嵌套内容,而实际上整个屏幕可能只是显示不到10个), 这样就会导致 diff
时间较长,同时由于是首屏渲染,会一次性构造很多 dom
,同样会影响首屏渲染的性能。
优化建议:
setData
数据量不易过大,不要一次性传递一个非常长的列表- 首屏时不要
一次性
构造太多节点,服务端可能一次请求过来有很多条数据,不要一次性setData
,可以先setData
一部分数据,然后等待一段时间(比如400ms,具体需要业务调节)再调用$spliceData
将其他数据传输过去
setData
setData
大概是小程序中使用最为频繁的接口了,因为页面上的任何变化都需要其去触发,但是能够触发页面去重新渲染的却不止一个 setData
。如下四个接口都会触发 webview
上页面的重新渲染。
Page.prototype.setData
: 触发整个页面做diff
Page.prototype.$spliceData
: 针对长列表做优化,避免每次传递整个列表,触发整个页面做diff
Component.prototype.setData
: 只会从对应组件节点开始做diff
Component.prototype.$spliceData
: 针对长列表做优化,避免每次传递整个列表,只会从对应组件节点开始做diff
优化建议:避免非常频繁的触发
setData
或者$spliceData
,不管是页面级别的还是组件级别的。在我们分析的案例中,有些页面有倒计时逻辑,但是有的倒计时过于频繁触发(ms级别的触发)。- 需要频繁触发重新渲染的时候,避免使用页面级别的
setData
和$spliceData
, 将这一块封装成自定义组件
,然后使用组件级别的setData
或$spliceData
触发组件重新渲染。 - 长列表的数据触发渲染时,使用
$spliceData
每次追加数据,而不必每次都传递整个列表。 - 复杂页面建议多封装成自定义组件,减少页面级别的
setData
示例:
推荐指定路径设置数据:
this.setData({
'array[0]': 1,
'obj.x':2,
});
而不是
const array = this.data.array.concat();
array[0] = 1;
const object={...this.data.obj};
obj.x=2;
this.setData({array,obj});
更不是(违反不可变数据原则)
this.data.array[0]=1;
this.data.obj.x=2;
this.setData(this.data)
长列表使用 $spliceData
this.$spliceData({ 'a.b': [1, 0, 5, 6] })
注意
有时候,我们将业务逻辑封装到了组件中,这样当仅仅需要组件对应的ui需要重新渲染时,我们只需要在组件内部调用 setData 触发组件重新渲染。但是,我们可能有时需要从页面上去触发组件重新渲染,比如我们在页面上监听了 onPageScroll 事件,当事件触发时,我们需要通知对应组件重新渲染,这个时候怎么做呢?
// page.js
Page({
onPageScroll(e) {
if (this.xxcomponent) {
this.xxcomponent.setData({
scrollTop: e.scrollTop
})
}
}
})
// component.js
Component({
didMount(){
this.$page.xxcomponent = this;
}
})
我们可以在组件的 didMount 中将组件挂载到对应的页面上,然后就可以在页面中调用组件级别的 setData 去仅仅触发组件的重新渲染了。
使用 key
在 for 中使用 key 来提高性能。
推荐
<view a:for="{{array}}" key="{{item.id}}"></view>
<block a:for="{{array}}"><view key="{{item.id}}"></view></block>
注意 key 不能设置在 block 上