One day, bored and wanting to practice speed, pull up a list of more than 10,000 items in a small application project. After loading more than 1000 entries, the list went blank. Take a look at the console:

“Dom Limit exceeded”, the number of Dom exceeded the limit. I don’t know why wechat will limit the number of Dom on the page.

A small program page limit how manywxmlNodes?

Wrote a little dome and did a test. ListData’s data structure is:

listData:[
   {
    isDisplay:true.itemList: [{qus:'Which of the following is Liu CAI CAI's girlfriend? '.answerA:'Liu Yifei'.answerB:'Dilieba'.answerC:Stray Bird Saito.answerD:'Parsley',}.../ / article 20 data]}]Copy the code

Page rendering effect:

1.dome1

<view wx:for="{{listData}}" class="first-item"  wx:for-index="i" wx:for-item="firstItem" wx:key="i" wx:if="{{firstItem.isDisplay}}">
     <view class="item-list" wx:for="{{firstItem.itemList}}" wx:key="index">
         <view>{{item.qus}}</view>
         <view class="answer-list">
              <view>A. <text>{{item.answerA}}</text></view>
              <view>B. <text>{{item.answerB}}</text></view>
              <view>C. <text>{{item.answerC}}</text></view>
              <view>D. <text>{{item.answerD}}</text></view>
         </view>
    </view>       
</view>
Copy the code

2. Dome2, remove unnecessary DOM nesting

<view wx:for="{{listData}}" class="first-item"  wx:for-index="i" wx:for-item="firstItem" wx:key="i" wx:if="{{firstItem.isDisplay}}">
     <view class="item-list" wx:for="{{firstItem.itemList}}" wx:key="index">
         <view>{{item.qus}}</view>
         <view class="answer-list">
              <view>A. {{item.answerA}}</view>
              <view>B. {{item.answerB}}</view>
              <view>C. {{item.answerC}}</view>
              <view>D. {{item.answerD}}</view>
         </view>
    </view>       
</view>
Copy the code

By rough calculation, a applet page can render about 20,000 WXML nodes while the applet official performance score is less than 1,000 WXML node official links

List page optimization

1. Reduce unnecessary label nesting

As the dome test above shows, minimizing the nesting of tags without compromising code performance and readability can greatly increase the number of tabular items on a page, since companies are not paid by lines of code. If you have a limited amount of list data, you can use this method to increase the number of list renders. If the data volume is very large and the number of nodes is more than 20,000, this method is not applicable.

2. Optimize the use of setData

As shown in Figure 5, the performance of the small program setDate is limited by the size of setData data and call frequency. Therefore, optimization should be carried out around reducing the size of setData data each time and reducing the frequency of setData calls.

(1) Delete redundant fields

Colleagues at the back end often take out data from the database and directly return it to the front end without any processing, which will lead to a lot of redundant data and many fields are not needed at all. We need to delete these fields and reduce the data size of setDate.

(2)setDataAdvanced usage of

Usually, the operation of adding, deleting and modifying data in data is to take out the original data, process it, and use setData to update the whole data. For example, more pull-up loading is used in our list, and data needs to be added to the tail of listData:

    newList=[{...},{...}];
   this.setData({
     listData:[...this.data.listData,...newList]
   })
Copy the code

This will result in the increasing amount of setDate data, and the page will become more and more jammed.

setDateCorrect use of posture

  • setDateModify the data

For example, if we want to modify the isDisplay property of the first element of the listData array, we can do this:

  let index=0;
  this.setData({
     [`listData[${index}].isDisplay`] :false,})Copy the code

What if we wanted to change the isDisplay property of elements with subscripts from 0 to 9 in the listData array at the same time? You might think of using a for loop to execute setData:

  for(let index=0; index<10; index++){this.setData({
        [`listData[${index}].isDisplay`] :false})},Copy the code

This will cause another problem, that is, listData calls too often, will also lead to performance problems, the correct way to handle is to first collect the data to modify, and then call setData once processing:

  let changeData={};
  for(let index=0; index<10; index++){ changeData[[`listData[${index}].isDisplay`]] =false;
  }
  this.setData(changeData);
Copy the code

This changes the isDisplay property of the elements with subscripts 0 through 9 in the listData array to false.

  • setDateAdd data to the end of the array

If only one piece of data is added

  letnewData={... };this.setData({
    [`listData[The ${this.data.listData.length}] `]:newData
  })
Copy the code

Add multiple pieces of data

  let newData=[{...},{...},{...},{...},{...},{...}];
  let changeData={};
  let index=this.data.listData.length
    newData.forEach((item) = > {
        changeData['listData[' + (index++) + '] '] = item // assign value, index increment
    }) 
  this.setData(changeData)
Copy the code

As for deleting operation, I have not found a better way, I wonder if you have any method to share?

Use custom components

By encapsulating one or more rows of a list into a custom component, using a component on a list page that counts only one node, you can multiply the amount of data your list can render. There is also a limit to the number of nodes within a component, but you can nest components layer by layer to achieve an infinite load of the list if you are bothered

Use virtual lists

After the above steps, the performance of the list is greatly improved, but if the amount of data is too large, the number of WXML nodes can exceed the limit, resulting in page errors. Our processing method is to use a virtual list, the page only render the current visual area and a number of nodes of data above and below the visual area, through isDisplay control node rendering.

  • Above the visible area:above
  • Visual area:screen
  • Below the viewable area:below

1.listDataArray structure

Use a two-dimensional array, because if it is a one-dimensional array, page scrolling requires setData to set a large number of element isDispaly attributes to control the rendering of the list. And a two-dimensional array can do this you can call setData and control the rendering of ten or twenty or more data ata time.

listData:[
   {
    isDisplay:true.itemList: [{qus:'Which of the following is Liu CAI CAI's girlfriend? '.answerA:'Liu Yifei'.answerB:'Dilieba'.answerC:Stray Bird Saito.answerD:'Parsley',}...// The number of items in the two-dimensional array depends on the actual project situation]}]Copy the code

2. Necessary parameters

   data{
       itemHeight:4520.// List the first dom height in RPX
       itemPxHeight:' '.// Convert to px height, because the applet gets the scrollbar height in px
       aboveShowIndex:0.// The first Index of rendered data
       belowShowNum:0.// Displays the number of hidden bars below the area
       oldSrollTop:0.// Record the height of the last scroll bar to determine the scrolling direction
       prepareNum:5.// The number of renderings above and below the viewable area
       throttleTime:200.// Time of rolling event throttling, in ms
   }
Copy the code

3.wxmlThe dom structure

    <! -- Above area -->
    <view class="above-box" style="height:{{aboveShowIndex*itemHeight}}rpx"> </view>
   <! -- actual rendered area -->
    <view wx:for="{{listData}}" class="first-item"  wx:for-index="i" wx:for-item="firstItem" wx:key="i" wx:if="{{firstItem.isDisplay}}">
        <view class="item-list" wx:for="{{firstItem.itemList}}" wx:key="index">
           <view>{{item.qus}}</view>
           <view class="answer-list">
                <view>A. {{item.answerA}}</view>
                <view>B. {{item.answerB}}</view>
                <view>C. {{item.answerC}}</view>
                <view>D. {{item.answerD}}</view>
           </view>
        </view>   
    </view>
    <! -- Below -->
    <view  class="below-box" style="height:{{belowShowNum*itemHeight}}rpx"> </view>
Copy the code

4. Get the px height of the dom layer 1 of the list

  let query = wx.createSelectorQuery();
  query.select('.content').boundingClientRect(rect= >{
    let clientWidth = rect.width;
    let ratio = 750 / clientWidth;
    this.setData({
      itemPxHeight:Math.floor(this.data.itemHeight/ratio),
     })
   }).exec();
Copy the code

5. Page scrolling time throttling

function throttle(fn){
  let valid = true
  return function() {
     if(! valid){return false 
     }
     // Work time, execute the function and set the status bit to invalid during the interval
      valid = false
      setTimeout(() = > {
          fn.call(this.arguments);
          valid = true;
      }, this.data.throttleTime)
  }
}
Copy the code

6. Handle page scrolling events

   onPageScroll:throttle(function(e){
    let scrollTop=e[0].scrollTop;// Scroll bar height
    let itemNum=Math.floor(scrollTop/this.data.itemPxHeight);// Calculate the visible area data Index
    let clearindex=itemNum-this.data.prepareNum+1;// Render the first index of the data after sliding
    let oldSrollTop=this.data.oldSrollTop;// Scrotop before scrolling, used to determine the direction of scrolling
    let aboveShowIndex=this.data. AboveShowIndex;// Get the index of the first rendered data
    let listDataLen=this.data.listData.length;
    let changeData={}
  // Scroll down
    if(scrollTop-oldSrollTop>0) {if(clearindex>0) {// The number of items to change after scrolling
          for(leti=aboveShowIndex; i<clearindex; i++){ changeData[[`listData[${i}].isDisplay`]] =false;
                let belowShowIndex=i+2*this.data.prepareNum;
                if(i+2*this.data.prepareNum<listDataLen){
                  changeData[[`listData[${belowShowIndex}].isDisplay`]] =true; }}}}else{// Scroll up
        if(clearindex>=0) {let changeData={}
         for(let i=aboveShowIndex-1; i>=clearindex; i--){let belowShowIndex=i+2*this.data.prepareNum
           if(i+2*this.data.prepareNum<=listDataLen-1){
            changeData[[`listData[${belowShowIndex}].isDisplay`]] =false;
           }
           changeData[[`listData[${i}].isDisplay`]] =true; }}else{
          if(aboveShowIndex>0) {for(let i=0; i<aboveShowIndex; i++){this.setData({
                [`listData[${i}].isDisplay`] :true,
              })
            }
          }
        }      
    }
    clearindex=clearindex>0? clearindex:0
    if(clearindex>=0&&! (clearindex>0&&clearindex==this.data.aboveShowIndex)){
      changeData.aboveShowIndex=clearindex;
      let belowShowNum=this.data.listData.length-(2*this.data.prepareNum+clearindex)
      belowShowNum=belowShowNum>0? belowShowNum:0
      if(belowShowNum>=0){
        changeData.belowShowNum=belowShowNum
      }
      this.setData(changeData)
    }
    this.setData({
      oldSrollTop:scrollTop
    })
  }),
Copy the code

After the above processing, the pagewxmlThe number of nodes is relatively stable, perhaps due to the index calculation error of the visual area data, the page rendering data fluctuates slightly, but it has not exceeded the limit of the number of nodes in the applet page at all. In theory a list of 1 million pieces of data shouldn’t be a problem, as long as you have the patience and energy to keep swiping and loading that much data.

7. Items to be optimized

  • The height of each row in the list must be fixed, otherwise it will cause errors in the index calculation of the visible area data
  • If the hand speed is too fast after rendering the playlist, the above and below areas will not be able to render the data, resulting in a temporary white screen. The white screen problem can be adjustedprepareNum. throttleTimeThe two parameters are improved, but cannot be completely solved (through the test comparison, it is found that even without any processing of the list, the situation of temporary white screen will occur if the sliding speed is too fast).
  • If you have images in the above and below fields, the images are cached locally and don’t need to go back to the server, but re-rendering takes time, especially if you have a very fast hand. And you can follow the idea above,isDisplayDestroy only non<image>The number of nodes will still increase, but it should be able to meet the needs of most projects, depending on how your project chooses.

Use custom components and virtual list comparison.

I don’t know why, but my gut tells me that using custom components is worse. In order to compare the advantages and disadvantages of the two methods, the performance of a 5000 strip image data is tested by using Trace tool.

Memory usage comparison:

Memory usage of custom components:

Virtual list memory usage:

As can be seen from the comparison, since components are not destroyed during pull-up loading, the amount of data increases gradually. Virtual lists destroy the same amount of data as they add, so the memory ratio stabilizes at a constant amount. In this test dome,5000 pieces of data using custom components ended up taking up 2000MB of memory, while the virtual list held steady at 700MB.

Comparison of the time taken to re-render after setData:

Custom component rerendering time:

Virtual list rerendering time:

As can be seen from the test results, the virtual list is superior to the custom component in terms of time distribution, maximum time and minimum time

Finally, attach the github address of the virtual list, if it is helpful to you, remember to give a little star

github