Suggestions on Performance Optimization

Operating Principle

Different from the traditional H5 applications, Mini Program operation architecture is divided into two parts -- webview and worker. The webview is for rendering, and the worker is for data storage and service logic execution.

  1. Communication between webview and worker is asynchronous. This means the data is not rendered immediately when setData is called, and asynchronous transmission from worker to webview occurs.
  1. During the transmission, the data is serialized as a string, and transferred by means of evaluateJavascript. The data size affects the performance.

Optimizing First Screen

The first screen may be defined differently. Here it means the first meaningful render of the service. For example: with regard to a list page, the first screen means the contents rendered for the first time in the list.

Controlling Size of Mini Program Resource Package

When the use accesses Mini Program for the first time,  mobile App client downloads Mini Program resource package from CDN, so the size of the resource package affects the Mini Program startup performance.

Optimization suggestions

  • Delete the useless image resources, because all image resources are packaged by default.
  • Control the size of images and avoid using large picture. It is recommended to upload large pictures via CDN channels.
  • Clear useless codes in time

Advance Data Request to onLoad

  • Upon operation, Mini Program triggers the onLoad lifecycle function of the page, and then transfers the initial page data from worker to webview for the initial render.
  • When the initial page render is completed, a notification is sent from webview to worker and triggers the onReady lifecycle function.

Some Mini Programs send requests in onReady which causes delay of first screen render.

Optimization suggestion

Advance data request to onLoad

Control the Number of Nodes to Be Rendered at Once in the First Screen

After the service request is returned, it generally calls the setData to trigger page re-render. The execution process is as below:

  1. Data is sent from worker to webview
  1. webview constructs virtual DOM as per the data transferred, makes difference comparison with the previous data (starting from the root node), and starts render.

Due to the data serialization in communication from worker to webview, and then the execution of evaluateJavascript in the webview, the first screen render performance is affected if the data transmitted once is too large.

in addition, if the construction nodes are too many or the nested hierarchy is too deep on webview, say, more than 100 list items to be rendered once in the list page of some Mini Program and each list item containing nested contents, but less than 10 items to be displayed on the whole screen, the different comparison takes long time, a large number of DOMs are constructed once in the first screen, and the first screen render performance is compromised.

Optimization suggestions

  • setData data quantity should not be too large; do not transfer too long list once.
  • Do not construct too many nodes on the first screen. The service end may request a large quantity of data once. Do not run setData all at once. It is possible to setData partial data and wait for a while (say, 400ms, depending on the specific service) and then call $spliceData to transfer the remaining data.

Optimize setData Logic

Any page change triggers setData. At the same time, multiple setData may trigger the page re-render. The following four interfaces trigger webview page re-render.

  • Page.prototype.setData: Triggers the difference comparison of the whole page
  • Page.prototype.$spliceData: Optimizes long list and avoid transferring whole list all at once and triggering the difference comparison of the whole page
  • Component.prototype.setData:Starts the difference comparison from the corresponding component node
  • Component.prototype.$spliceData: Optimizes long list and avoid transferring whole list all at once. Only makes difference comparison from the corresponding component node.

Optimization suggestions

  • Avoid triggering setData or $spliceData frequently, no matter on the page level or component level. In our analyzed cases, some pages contain countdown logic but the countdown is triggered too frequently (in microseconds).
  • When it is required to trigger re-render frequently, avoid using page-level setData or $spliceData. This block can be encapsulated into a custom component, and then the component-level setData and $spliceData can be used to trigger component re-render.
  • For render of long data list, use $spliceData to append data in several times instead of transfer of the whole list.
  • For complicated page, it is recommended to encapsulate it into custom component to minimize the page-level setData.

Optimization case

Suggest specifying path to set data:

copy
this.setData({
  'array[0]': 1,
  'obj.x':2,
});

Not suggesting the following method (although this.data is copied, the attribute is changed directly):

copy
const array = this.data.array.concat();
array[0] = 1;
const obj={...this.data.obj};
obj.x=2;
this.setData({array,obj});

Even not suggesting direct change of this.data (violating the immutable data principle):

copy
this.data.array[0]=1;
this.data.obj.x=2;
this.setData(this.data)

Using $spliceData for long list

copy
this.$spliceData({ 'a.b': [1, 0, 5, 6] })

Note:

Sometimes when service logic are encapsulated in component, it is only required to call setData within the component when the component UI needs re-render. In other occasions, however, it is required to trigger component re-render from the page. For example, the onPageScroll event is monitored on page, and it is required to notify the corresponding component to render again when the event is trigger. Now the measure is as below:

copy
// /pages/index/index.js
Page({
    onPageScroll(e) {
        if (this.xxcomponent) {
            this.xxcomponent.setData({
                scrollTop: e.scrollTop
            })
        }
    }
})
// /components/index/index.js
Component({
    didMount(){
        this.$page.xxcomponent = this;
    }
})

It is possible to mount the component to the corresponding page in the didMount, so that the call of component-level setData in the page triggers re-render of the component only.

Use Key Parameter

The “key” can be used in “for” to increase performance. Note that the “key” cannot be set on blocks.

Sample codes:

copy
<view a:for="{{array}}" key="{{item.id}}"></view>
<block a:for="{{array}}"><view key="{{item.id}}"></view></block>