Author: Concave-Mann-JJ

background

2020 is a year of community group buying, the Internet companies have seized a minute a second to run into the field. “Jingxi Pin-pin” is a community group-buying platform of JINGdong. Relying on the supply chain system of JINGdong, it selects high-quality goods at low prices and provides high-quality services such as next-day delivery for community users.

The technology selection of the JINGxi Pinpin team uses Taro to meet multiple requirements. Therefore, the Taro team is honored to participate in the performance experience optimization of “Jingxi Pinpin” mini program.

Comprehensive experience – Review the best practices of Taro writing

After a thorough experience and familiarity with business code, we’ve come up with a series of best practices for Taro3 writing:

1. Performance

There are two factors that greatly affect the performance of the small program, namely the amount of setData data and the number of calls of setData function per unit time.

When you encounter performance problems, printing setData data in your project can be very helpful to help locate the problem. Developers can search for the location of the call to locate.setData by entering the dist/taro.js file of the Taro project, and then print the data.

In Taro, batch bundle update operation will be performed for setData, so most of the time, only the size of setData data needs to be considered.

The following are some of the writing problems that developers need to pay attention to. Some of them need to be manually adjusted by developers. Taro can help automate some of them:

1.1. Exercise caution when deleting a floor node

Suppose there is a structure like this:

<View>
  <!-- 轮播 -->
  <Slider /><! -- Merchandise Section --><Goods /><! -- modal popover --> {isShowModal &&<Modal />}
</View>
Copy the code

Taro3’s current node deletion handling is flawed. When isShowModal changes from True to false, the modal popover disappears. At this point, all sibling nodes of the Modal component are updated, and setData data is DOM node information of Slider + Goods component.

In most cases, the impact is not so great that there is no mental burden on the developer. However, if the DOM structure of the sibling node to be deleted is very complex, such as floor components, the side effect of deletion operation will lead to a large amount of setData data, thus affecting performance.

Solutions:

Now we can optimize and isolate delete operations like this:

<View>
  <!-- 轮播 -->
  <Slider /><! -- Merchandise Section --><Goods /><! -- Modal popover --><View>
    {isShowModal && <Modal />}
  </View>
</View>
Copy the code

We are optimizing the algorithm for node deletion to completely avoid such unnecessary setData, which was introduced in V3.1.

1.2. Properties of the underlying component are kept as references as possible

If the attribute value of an underlying component (View, Input, etc.) is assumed to be a non-primitive type, try to keep references to the object.

Suppose the following is written:

<Map
  latitude={22.53332}
  longitude={113.93041}
  markers={[{
    latitude: 22.53332.longitude: 113.93041}]} / >Copy the code

On each render, React makes a shallow comparison of the attributes of the base component, and updates the component’s attributes when it sees different markers referenced. Finally, the number of setData increases and the amount of setData increases.

Solutions:

References to objects can be kept by means of state, closures, and so on:

<Map
  latitude={22.53332}
  longitude={113.93041}
  markers={this.state.markers}
/>
Copy the code

1.3. Try not to mount additional properties on applets base components

If the basic components (such as View, Input, etc.) set non-standard attributes, these additional attributes will be setData, and in fact, the small program does not care about these attributes, so the setData part of the data is redundant.

For example, the standard properties of the Text component are selectable, user-Select, space, and decode. If we set an additional property something for the Text component, this additional property will also be setData.

<Text something='extra' />
Copy the code

Taro V3.1 will automatically filter these additional attributes and this restriction will no longer exist.

2. Experience relevance

2.1. Rolling penetration

In small program development, when sliding overlay elements such as masks and popovers, the sliding event will bubble to the page, causing the page elements to slide as well.

Due to the limitations of the Taro3 event mechanism, applets events are bound in the form of BIND. So unlike Taro1 and Taro2, calling e.topPropagation () does not prevent scrolling propagation.

Solutions:
  1. Use style resolution (recommended)

Write a style for the component that needs scrolling disabled like this:

{
  overflow: hidden;height: 100vh;
}
Copy the code
  1. catchMove

For very few components, such as Maps, styling a fixed width and height will not prevent scrolling because these components are inherently capable of scrolling. So the first approach does not handle rolling events that bubble up to the Map component.

Add the catchMove property to the View component:

// This View component is bound to the CatchTouchMove event instead of the BindTouchMove event
<View catchMove />
Copy the code

2.2. Jump preloading

In the small program, there will be a delay from calling the taron. navigateTo jump class API, until the new page triggers onLoad. Thus operations such as network requests can be preceded by calls to the jump API.

Students familiar with Taro may recall the componentWillPreload hook in Taro1, Taro2. Taro3 no longer provides this hook, and developers can use the taron.preload () method to implement jump preloading:

// pages/index.js
Taro.preload(fetchSomething())
Taro.navigateTo({ url: '/pages/detail' })
Copy the code
// pages/detail.js
console.log(getCurrentInstance().preloadData)
Copy the code

2.3. It is recommended to save the taro.getCurrentInstance () result

Taro. GetCurrentInstance () is often called during development to obtain the app, Page object, routing parameters and other data of the applet. But calling it frequently can cause problems. It is therefore recommended to store the taron.getCurrentInstance () result in the component and use it directly afterwards:

class Index extends React.Component {
  inst = Taro.getCurrentInstance()

  componentDidMount () {
    console.log(this.inst)
  }
}
Copy the code

Tough bones – Shopping cart page

We struggled with performance on the lower end of the phone, especially on the shopping cart page. By analyzing the page structure and reflecting on Taro’s underlying implementation, we mainly adopted two optimization measures, which improved the scrolling smoothness of low-end models and reduced click delay from 1.5s to 300ms.

1. Long list optimization

In Taro3, we have added a special component called virtual lists, which helps developers in many communities optimize long lists. Many of you are familiar with the implementation principle of virtual lists, including the diagram below, but the shopping cart page presents us with new requirements.

1.1 No height restriction

The virtual list calculates the position of each node according to itemSize. If the width and height of nodes are uncertain, it is difficult to judge the real size of the list until each node is loaded at least once. This is why we didn’t support this feature in earlier versions of virtual lists, choosing instead to fix the height of each node to avoid the mental burden of using virtual lists.

This requirement is not impossible, however, and can be easily implemented by simply tweaking the logic used to implement and use virtual lists.

import VirtualList from `@tarojs/components/virtual-list`

function buildData (offset = 0) {
  return Array(100).fill(0).map((_, i) => i + offset);
}

- const Row = React.memo(({ index, style, data }) => {
+ const Row = React.memo(({ id, index, style, data }) => {
  return (
- 
      
+ 
      Row {index} </View> ); }) export default class Index extends Component { state = { data: buildData(0), } render() {const {data} = this.state const dataLen = data.length return (<VirtualList height={500} // list height ItemData ={data} itemCount={dataLen} itemSize={100} // itemSize={100} // The height of an item in the list+ unlimitedSize={true} // Unlimit the list node size> {Row} // list single component, here can only pass one component </VirtualList>); }}Copy the code

As you can see, we in the new id passed to help get read after the initial load of each node in its true size, thanks to Taro cross-platform advantage, this is the simplest step in refactoring virtual list component, with this basis, the actual size we can node location information associated with them together, Let the list itself adjust the position of each node and present it to the user.

For developers who want to use this mode, simply pass in unlimitedSize to let the virtual list unlimit its height. Of course, this does not mean that you do not need to pass in node sizes when using virtual lists. ItemSize in this mode is used as an initial value to assist in the calculation of the location of each node in the list.

If the itemSize is too different from the actual size, there will be obvious problems in the long list

1.2 Bottom of the List

The bottom area of the list makes it easy to display information, such as pull-up loading, as well as virtual lists.

Return (<VirtualList height={500} // list height width='100%' // list width= itemData={data} // Render list data itemCount={dataLen} // ItemSize ={100} // The height of a list itemRenderBottom ={
       
      }> {Row} // list single component, here can only pass one component </VirtualList>);Copy the code

Note that scrollOffset > ((dataLen – 5) * itemSize + 100) is used to determine the bottom of the VirtualList document. This is because we do not return the scrolling details in VirtualList. This time we will also return relevant data to help you use virtual lists better.

Interface VirtualListEvent<T> {/** Scroll direction, possible values are forward and backward. * / scrollDirection: 'forward' | 'backward' / * * * / scrollOffset rolling distance: Number / * * when rolling by scrollTo () or scrollToItem () call returns true, otherwise it returns false * / scrollUpdateWasRequested: Boolean /** Currently only React supports */+  detail?: {
+ scrollLeft: number
+ scrollTop: number
+ scrollHeight: number
+ scrollWidth: number
+ clientWidth: number
+ clientHeight: number
+}
}
Copy the code

1.3 Performance Optimization

In a virtual list, either layout will result in page backflow, so it doesn’t make much difference which one you choose to render the page in the browser kernel. However, with Relative, there are far fewer node styles to adjust for the list. That’s why we’ve added this location model to our new virtual list, which is free for developers to choose from. For low end models, relative mode has been able to give virtual lists a good experience on low end models until we have optimized the overall rendering performance.

2. Rendering performance optimization

Taro3 uses the template of the applet for rendering and does not normally use native custom components. This leads to the problem that all setData updates are called by page objects, and if we have a complex page structure, the performance of the updates will degrade.

The data structure of setData when the hierarchy is too deep:

page.setData({
  "root.cn.[0].cn.[0].cn.[0].cn.[0].markers": []})Copy the code

To solve this problem, the main idea is to borrow native custom components of small programs to achieve the effect of local update, so as to improve the update performance.

Expected setData data structure:

component.setData({
  "cn.[0].cn.[0].markers": []})Copy the code

Developers can achieve this optimization in two ways:

2.1 Global Configuration item baseLevel

For applets that do not support template recursion (wechat, QQ, jingdong applets), Taro will use native custom components to assist recursion once the DOM level reaches a certain number.

After N layers of DOM structure, native custom components are used for rendering. N The default layer is 16. You can modify the baseLevel configuration item to change the layer N.

Setting baseLevel to 8 or even 4 levels can greatly improve update performance. However, the setup is global and causes several problems:

  1. flexThe biggest problem is that layouts fail across native custom components.
  2. SelectorQuery.selectmethodsDescendant selectors across custom componentsThe notation needs to be increased>>>:.the-ancestor >>> .the-descendant

2.2 CustomWrapper components

To address the inflexibility of global configuration, we added a base component, CustomWrapper. Its function is to create a native custom component, the setData of the descendant node will be called from this custom component, to achieve the effect of local update.

Developers can use it to wrap modules that encounter performance issues with updates to improve performance when they are updated. Because the CustomWrapper component needs to be used manually, developers can understand that “this layer uses custom components and needs to avoid the two problems of custom components”.

example
<CustomWrapper>
  <GoodsList>
    <Item />
    <Item />
    // ...
  </GoodsList>
</CustomWrapper>
Copy the code

Perfect – Average experience rating is 95+

The developer tool will identify all components bound to click events. If the area of the component is too small, it will indicate that the click area is too small, which will affect the score of the “experience item”. But Taro3 binds all properties and events to the component by default. This “accidentally hurts” some components that are small and don’t actually have clickable functionality, but because Taro3 is bound to events by default, the clickable area is considered too small by the developer tools, driving down the experience score.

Template for the Text component, with all properties and events bound by default:

<template name="tmpl_0_text">
  <text
    selectable="{{... }}"
    space="{{... }}"
    decode="{{... }}"
    user-select="{{... }}"
    style="{{... }}"
    class="{{... }}"
    id="{{... }}"
    bindtap="..."
  >.</text>
</template>
Copy the code

Therefore, we set up a static template for View, Text and Image components. When detecting that the component has no binding event, we use the static template to avoid “accidental damage”.

On the other hand, this move also reduces the number of DOM binding events for applets, resulting in a slight improvement in performance, and the reduction in attributes makes the DEVELOPER tools XML palette clearer for debugging. However, there are drawbacks to this approach, which can lead to a slight increase in the size of the compiled Base.wxml, which is still worth the performance trade-off.

Static template for the Text component with no binding events:

<template name="tmpl_0_static-text">
  <text
    selectable="{{... }}"
    space="{{... }}"
    decode="{{... }}"
    user-select="{{... }}"
    style="{{... }}"
    class="{{... }}"
    id="{{... }}"
  >.</text>
</template>
Copy the code
Optimized shopping cart page experience score

Another battleground – multi-end adaptation & native hybrid

After finishing our support trip with a pot of sheep scorpions, we finally got a sunny day in the south. But the work is not over. There are still two pieces of work to follow.

Adaptation jingdong small program

The process of adapting jingdong mini program is relatively smooth, and there are few changes to be made.

Taro3’s major upgrades along the way are enhanced PARSING of HTML text and support for

Taro3 is mixed with native projects

In the past, we have supported a mix of native uses in the Taro project. Conversely, there is little emphasis on mixing Taro with native projects. However, there are plenty of native development applets on the market, and the transformation cost for them to integrate Taro’s development is often very high, so they have to give up the idea of hybrid development at last.

After this project, we are also motivated to pay more attention to this part of the demand. After Taro V3.0.25, we have launched a complete set of native projects using Taro.

The solution mainly supports three scenarios:

  1. Use the pages developed by Taro in native projects. (Completed)
  2. Run complete Taro projects in subcontracting of native projects. (Completed)
  3. Use custom components developed by Taro in native projects. (Under development)

We hope that the above solutions will meet the needs of developers who wish to gradually join Taro. For more comments, please leave us a comment on Github.

The end of the

Taro’s team participated in the performance experience optimization of “Jingxi Pinpin”, which made us understand the performance bottleneck of Taro3 and realize the diversity of complex business.

In the first half of 2021, we will focus more on improving the framework development experience and performance, blending with native applets, and eco-building.

Finally, I wish you all a happy Spring Festival ~ the New Year!

Read the original