The cause of

Because the company wants to develop multi-terminal small programs, after research, we selected the Taro framework of Concave-Convex Laboratory. Before we used native language to write wechat small program, now we will use Taro to turn over the code of wechat small program.

Encountered a page is like this, there are many brand names on the page, you can browse the brand name by dragging the page, you can quickly locate the brand name list by clicking the letter in the right sidebar, you can also enter the letter to match the compound brand name. Click to select or deselect the brand name, the selected brand name will be listed above. Click the clear button at the bottom to clear all selected states. Click OK to indicate that the selection is complete and return to the previous page. The process is shown as follows:

Looks very simple code, the original code logic moved to use: the brand name contains a special character processing, according to the first letter of the brand name divided into a two-dimensional array, at the same time maintain a Map to facilitate quickly find the need to deal with the data. Put the data into the ScrollView component, then calculate the height by the number of brand names, control the first letter of the top brand on the current page, and use the scrollIntoView component to handle the positioning of the letters in the sidebar. I looked at the source code for about two days, and then spent another two days moving the code in and happily submitting it. I went to work after the holiday. I ran a build and tested it with a real machine. And then found that the page card can not bear to look at, no performance at all, so began to step on the road of various technical solutions.

The road to filling holes

Causes of poor performance

It is mentioned in the official documentation of Taro

Taro Next needs to go through the following steps when a page loads:

  1. The React/Nerv/Vue framework renders pages into the virtual DOM
  2. The Taro runtime serializes the page’s virtual DOM into renderable data and drives page rendering using setData()
  3. The applets themselves render the serialized data

Steps 1 and 2 are redundant compared to native or compiled applets frameworks. If there is no performance problem in the business logic code of the page, most of the performance bottleneck is in setData() in Step 2: since the initial rendering is the entire virtual DOM tree of the page, the data volume is quite large, so setData() needs to pass a relatively large data, resulting in a period of blank screen during the initialization of the page. This usually happens when the number of WXML nodes in the initial rendering of the page is high or when the user machine performance is low.

Looking at the number of brands, there were more than 2,000. With more than 2,000 nodes, Taro has been frozen for several seconds.

Question list

  1. After entering Brands, the page will appear white screen for a long time and rendering will be slow.
  2. The page rerenders very slowly when clicking to select the brand name.
  3. A, B, C, D, etc. will be displayed at the top of the brand list to display the first letter of the current page brand. It is very slow to switch when sliding
  4. Click on the letter bar on the right, and it is very slow to locate the selected brand

Question one and question two

Both of these issues deal with rendering and are Virtual List practices, so they are handled together.

pre-rendered

Official documents are pre-rendered and added to /config/index.js or /config/dev.js or /config/prod.js

const config = {
  ...
  mini: {
    prerender: {
      match: 'pages/**/brands
    }
  }
};

module.exports = config
Copy the code

However, an error was reported after build, taro reference package error. Without finding a solution, the operation was abandoned.

First render some data

The solution is a pre-render, starting with about 70 brand names (roughly the number of pages that can be swiped down for the duration of the white screen, which is about 6 pages long). Let the page load data quickly to complete the rendering. After the page loads, re-render all brands.

OnReady () {Taro. NextTick (() => {// load all brand names})}Copy the code

After this processing, page loading can be done in seconds, but click on the selected brand is still very slow. So it needs to be optimized.

Componentization of brand name

With a portion of the data rendered above, line by line brand names are made into components. Render using isSelected of props passed in externally, and then click interaction using the internal state of the component. Click the event to upload the selection status to the brand page and update the data.

function Index(props) { const { name, isSelected, onTap } = props const [ selected, setSelected ] = useState(isSelected) const handleClick = (status) => { setState(! status) onTap & onTap(! } return (<View className= "onClick={handleclick. bind(this, selected} > {name} </View>)}Copy the code

After doing this, the rendering was still slow and there was no way outside to change the state inside the component. Page click the “clear” button, the selected state cannot be cleared. Can only give up.

Virtual List

After using Virtual List, the above two problems can be perfectly solved. The disadvantage is that in the case of fast slide page, the page will remain blank before rendering, and when the slide stops, the page will render the product name.

Problem 3: A, B, C and D will be displayed at the top of the brand list to display the first letter of the current brand on the page. It is slow to switch when sliding

The CSS to solve

Position: sticky can be convenient to suck the top problem. Set element CSS:

view {
  position: sticky;
  top: 20px;
}
Copy the code

The element is positioned according to the normal document flow and is offset by the top, left, right, and bottom of the nearest ancestor element that has a scrolling mechanism (no overflow: Visible attribute). Position: sticky The problem of changing the first letter of a brand is solved by using CSS code, but when the element changes to Position: sticky, the list of corresponding brands displayed with the first letter of the element will slide off the screen. When selecting the right quick location, only A, B, C… The first letter is at the top, and the corresponding brand name list does not appear on the screen. I’m going to give up this easy trick.

IntersectionObserver

Set A IntersectionObserver, through IntersectionObserver relativeToViewport fixed position reference, if the intersection, it will be placed at the top of the first letters into the intersection of A, B, C, D initials, etc. However, when the initial letter changes, the UI of the page will also change accordingly, which will cause changes in the Observer. In the case of the boundary, two letters will flicker continuously. Gave up again.

Calculate height

Count the number of all brand lists for each initial letter and calculate the height of each initial brand by multiplying the number by the UI height of the individual brand name to get a height array. Through scroll to get scrollOffset, fall in which height interval will display which initial letter. The scheme is feasible.

Problem 4: It is very slow to locate the selected brand by clicking the letter bar on the right

The ScrollView scrollIntoView

You can set one element for each initial position and set the ID. You can use scrollIntoView to quickly locate it. But the rendering of ScrollView is very slow. To give up

The scrollTop VirtualList

In the same way as problem 3, click on the initial letter to quickly get the starting position of its height, and set the value of scrollTop to quickly locate it. One drawback is that after positioning, moving a short distance, clicking the same location, does not happen to jump. A little bit of code. The code is as follows:

const handleScrollToTitle = (title: String) = > {const offset = brandLetters. Get (title) | | 0 / / get the initial height in the first letter in the queue if (offset = = = scrollTop) {/ / Equality is the same letter clicked twice, SetScrollTop (offset-1) setTimeout(() => setScrollTop(offset), 0)} else {setScrollTop(offset)}}Copy the code

Using Virtual List gives you the following effect

Using Virtual List for the first time, I sorted out the implementation of the whole page and encountered problems. Special thanks to Alex for the various solutions in the sidebar that helped me finish this page! You can experience it, from the screen, there is a brand, click on it.