Talk about React Native in 2020. I hesitated before writing this article, but I realized that writing technical articles is not a fad, so I decided to write this article on React Native performance optimization.

The React Native performance optimizations discussed in this article do not go as far as modifying the React Native source code, so they are versatile enough to be useful to most RN developers.

This article is part of the official optimization recommendations for React/RN/Android/iOS, part of the optimization points found through source code, and part of the excellent open source framework that can solve some performance bottlenecks. This article summarizes the content you rarely see online, so you’re sure to get something out of it. If you think it is good, please do not spare your likes, share this article of more than 1W words, so that more people see it.

Before reading this article, it should be noted that some optimization recommendations are not applicable to all teams. Some teams use React Native as an enhanced webpage, some teams use React Native for non-core functions, and some teams use React Native as a core architecture. Different positioning requires different selection. I’ll mention these scenarios as well, but it’s up to the developers to decide how to use them.

2021 added

This article focuses on “rendering layer” optimization. If you need to start speed optimization, check out my new article:

⚡️ React Native startup Speed Optimization — Native

⚡️ React Native startup speed optimization – JS


Directory:

  • First, reduce re-render
  • Two, reduce the rendering pressure
  • Three, picture optimization those things
  • Separation of object creation calls
  • 5. Animation performance optimization
  • Long list performance optimization
  • React Native performance optimization tools
  • Recommended reading


First, reduce re-render

React Native is also a part of the React ecosystem, so many React optimization techniques can be used here. Therefore, this article starts with the most familiar places.

Reducing re-render was arguably the most profitable thing for React.

1 ️ ⃣ shouldComponentUpdate

📄 document: react.docschina.org/docs/optimi…

Simple examples:

class Button extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color ! == nextProps.color) {return true;
    }
    return false;
  }

  render() {
    return <button color={this.props.color} />; }}Copy the code

ShouldComponentUpdate is sure to be a hot topic when it comes to React performance optimization.

Using this API, we can get the state/props of the before and after state and manually check if the state has changed and then determine if the component needs to be rerendered based on the changes.

ShouldComponentUpdate: shouldComponentUpdate: shouldComponentUpdate: shouldComponentUpdate: shouldComponentUpdate: shouldComponentUpdate: shouldComponentUpdate: shouldComponentUpdate: shouldComponentUpdate: shouldComponentUpdate In the actual project, China Literature Group’s 🔗 React Native application “Yuanqi Reading” has also made a good demonstration, and 🔗 Twitter performance optimization sharing has also made a good reference, students who are interested in this can click to jump to check.

I would like to remind here that shouldComponentUpdate is strongly business logic relevant. If you use the API, you have to consider all the props and states associated with the component, and if you miss anything, you can get data and view disunity. So be very careful when you use it.Copy the code

2 ️ ⃣ ️ React. Memo

📄 document: react.docschina.org/docs/react-…

React. Memo is a new feature introduced in React V16.6. It is an advanced component specifically for the React function component.

By default, it is shallow, like PureComponent, because it is a higher-order component and can be layered on top of the original component:

const MemoButton = React.memo(function Button(props) {
  return <button color={this.props.color} />;
});
Copy the code

If you want to customize the comparison procedure as shouldComponentUpdate does, react. memo also supports passing in custom comparison functions:

function Button(props) {
  return <button color={this.props.color} />;
}
function areEqual(prevProps, nextProps) {
  if(prevProps.color ! == nextProps.color) {return false;
    }
  return true;
}
export default React.memo(MyComponent, areEqual);
Copy the code

ShouldComponentUpdate () returns the exact opposite of the function shouldComponentUpdate. If the props areEqual, areEqual() returns true. ShouldComponentUpdate returns false.

3 ️ ⃣ React. PureComponent

📄 document: react.docschina.org/docs/react-…

Simple examples:

class PureComponentButton extends React.PureComponent {
  render() {
    return <button color={this.props.color} />; }}Copy the code

ShouldComponentUpdate, React has a similar component called React.PureComponent, which makes a shallow comparison between props and state before the component is updated. So shouldComponentUpdate should be awkward when there are too many levels of data nesting, such as if you want to pass in a two-layer nested Object for props: Should I update or not update?

Given the above, I rarely use PureComponent in projects. It’s easy to use, but it’s not as simple as using shouldComponentUpdate to manually manage complex logic. Of course, this is just a personal development habit, but there are other solutions on the community:

  • Divide components into smaller sub-components and use them collectivelyPureComponentManage rendering timing
  • useImmutable object, coupled withPureComponentCompare the data (🔗 reference link:React optimization is awesome)
  • .

Different people have different opinions on this issue, on the premise of not affecting the function, mainly depends on the team selection, as long as the agreement is good in advance, in fact, the workload in daily development is about the same (after all, not every page is necessary for performance optimization).

4 ️ ⃣ React. UseMemo and React. UseCallback

📄 document: zh-hans.reactjs.org/docs/hooks-…

Since React16 was released, the React team has focused on functional components and hooks, where the official useMemo and useCallback can be fine-tuned for performance. Specific use can directly see the official documentation.

5️ skilled use of redux

Let’s say an RN project uses Redux to manage global state. When developing a new Page from zero, we tend to pack a layer of React-Redux on the entire Page and pass redux data in, since the previous project is simpler and less logical. This is ok in the early stage.

But as the project iterates, the logic of the page is bound to become more complex, with more referenced components and dependent Redux states. This can lead to a problem: any dependent redux substate change can cause re-render of the entire Page, even if only a small part of the Page is dependent on that state.

Instead of wrapping the entire Page with react-Redux, use react-Redux to wrap components that rely directly on redux data to minimize the performance impact of component redrawing.


Two, reduce the rendering pressure

React Native’s layout system relies on 🔗 Yoga, a cross-platform layout library, to map virtual DOM to Native layout nodes. In 99% of Web development cases, a Virtual DOM corresponds to a real DOM. Does React Native have a one-to-one relationship? Let’s write a simple example to explore that.

The first card contains a yellow View, and the second card contains an empty View:

// The following sample code retains only the core structure and style
render() {
  return (
    <View>
      <View style={{backgroundColor: 'orange'}} >
        <View style={{backgroundColor: 'yellow'}} >
          <Text>Card2</Text>
        </View>
      </View>
      <View style={{backgroundColor: 'orange'}} >
        <View>
          <Text>Card2</Text>
        </View>
      </View>
    </View>
  );
};
Copy the code

The react nested hierarchy looks like this with react devtools:

As you can see from the diagram above, the React component and the code structure still correspond one to one.

Take a look at the React Native rendered to the Native View’s nested hierarchy (Debug View Hierarchay for iOS, Layout Inspector for Android) :

In iOS, a React node corresponds to a native View node. Android second card blank View is missing!

React Native filters views with layout attributes (LAYOUT_ONLY_PROPS) before the React Native Android UI layout to reduce View nodes and nesting. More friendly to fragmented Android.

The React component does not map to the native View in a one-to-one way. How can we optimize the layout?

1️ react. Fragment to avoid multi-layer nesting

📄 React Fragments could document: zh-hans.reactjs.org/docs/fragme…

Let’s start with the most familiar place — react.Fragment. The React API allows a React component to return multiple nodes, which is easy to use:

render() {
  return (
    <React.Fragment>
      <ChildA />
      <ChildB />
      <ChildC />
    </React.Fragment>
  );
}

// Or use the Fragment phrase
render() {
  return (
    <>
      <ChildA />
      <ChildB />
      <ChildC />
    </>
  );
}
Copy the code

Fragments are quite useful: they prevent you from writing an extra layer of views. React Native components (such as ScrollView or Touchable* components). Use this property to reduce the nesting level of your View.

2️ reducing GPU overdrawing

In business development, we often encounter such a scenario: the background color of the entire interface is white, and then a card component with a white background is added to it, and inside the card there is a widget with a white background……

// The following sample code retains only the core structure and style
render() {
  return (
    <View>
      <View style={{backgroundColor: 'white'}} >
        <View style={{backgroundColor: 'white'}} >
          <Text style={{backgroundColor: 'white'}} >Card1</Text>
        </View>
      </View>
      <View>
        <View>
          <Text>Card2</Text>
        </View>
      </View>
    </View>
  );
};
Copy the code

First of all, let’s make it clear that the color of each pixel on the screen is determined by the color of multiple layers, and the GPU will render the final color of these layers after blending. However, iOS and Android GPU rendering mechanism is not the same.

Although the above code ends up rendering white on display, GPU optimization is different. We used iOS Color Blended Layers and Android 🔗 GPU overdraw debugging tool to see the final rendering result:

For iOS, a red area indicates a color mix:

  • All views of Card1 have an opaque background color, and the GPU no longer calculates the color of the bottom layer after it gets the top color
  • The background color of the Text View of Card2 is transparent, so the GPU has to get the color of the next layer for blending

For Android, the GPU unnecessarily renders pixels that are not visible to the user. There is a color indicator bar: white -> blue -> green -> pink -> red, the color later indicates the more serious overdrawing.

  • Several views of Card1 have an opaque background color, with red indicating at least four overdraws
  • In Card2, only the text is overdrawn

In the test of transition drawing, the results of iOS and Android experiments are almost completely opposite, so the solution is definitely not the best of both sides. In my opinion, when developing React Native for view optimization, Android should be optimized first, so we can optimize from the following points:

  • Reduce background color repetition: Setting a background color for every View on Android can cause serious overdrawing; React Native also reduces the nesting of Android layouts with only layout properties
  • Avoid translucent colors: Translucent colors can cause overdrawing on iOS and Android
  • Avoid rounded corners: Rounded corners can cause overdrawing on both iOS and Android
  • Avoid shadows: Shadow areas on iOS and Android can cause overdrawing
  • .

Avoid excessive details drawn by GPU. General pages do not need such fine management, so long list optimization can be considered in this direction.


Three, picture optimization those things

Another big part of performance optimization is graphics. Image optimization here not only refers to reducing Image size and HTTP bandwidth usage, I will talk more about some Image component optimization, such as cache control, Image sampling and other technologies.

Optimization item of ️1️ discount Image component

React Native’s Image component, if only used as a normal Image display component, has everything it should have. For example:

  • Load local/network images
  • Automatically match @2x/@3x pictures
  • Image loading event:onLoadStart/onLoad/onLoadEnd/onError
  • Loading Default view or loading indicator
  • .

However, if you want to use it as an Image download management library, it will be very uncomfortable, because these attributes of Image have different performance on iOS/Android, some implemented and some not implemented, it is very uncomfortable to use.

Before we get into image optimization, let’s think about what a basic image download management library would accomplish:

  1. Image types: First of all your main responsibility is to load images, you can load at least a variety of image types
  2. Download management: it can manage multiple requests and control the priority of image loading when loading multiple images
  3. Cache management: do a good job of three-level cache, not every picture to request network, balance the strategy of memory cache and disk cache
  4. Multi-image loading: how to make images load quickly and reduce lag when many images are rendered simultaneously

According to the above four principles, let’s analyze the Image component one by one.

1. Picture type

Basic PNG/JPG /base64/ GIF format, good support. Note, however, that you need to add some dependencies to build. Gradle if you want Android to load giFs to move. For details, see 🔗.

If you want to load images in WebP format, there are some problems. As a kind of image format launched by Google, Android naturally supports it, but iOS does not support it, requiring us to install some third-party plug-ins.

2. Download management

First, the Image component’s ability to download and manage images is basically 0.

Image can only be monitored with a single Image loading process: basically onLoadStart/onLoad/onLoadEnd/onError, if you want to download priority control multiple pictures, I’m sorry, no.

3. Cache management

Caching is managed through HTTP headers and directly through some component properties.

When the Image component requests a network Image, it can add an HTTP header, so that it can use the HTTP cache to manage the Image, as shown in the following code:

<Image
  source={{
    uri: 'https://facebook.github.io/react/logo-og.png'.method: 'POST'.headers: {
      Pragma: 'no-cache',},body: 'Your Body goes here',
  }}
  style={{width: 400.height: 400}} / >Copy the code

Specific control parameters can be found in 🔗 MDN HTTP cache, which is not detailed here.

Control image caching directly via properties, iOS has. Android? Sorry, no.

IOS can control the cache through the cache field in the source parameter, and the attributes are very common: default/no cache/strong cache/cache only. For details, see 🔗 iOS Image cache documentation.

4. Multi-graph loading

In the 5G era, short video /VLog is being used every day, not to mention multi-picture scenes, which is basically standard for Internet applications.

Before you load an image, make sure you have a concept: image file size! = The size of the image after loading into memory.

JPG PNG webP, as we often say, is a compressed file of the original image, which is conducive to disk storage and network transmission, but when displayed on the screen, it will be restored to the original size.

For example, a 1024×768 PNG image may occupy more than 10 KB of disk space, regardless of the resolution and other issues, loading into memory will take up 3 Mb.

// Different resolution/folder/encoding format will bring numerical differences
// The following calculation is just the most general scenario

(1024 * 768 * 4 * 8)/(8 * 1024 * 1024) = 3MB (length * width * bytes per pixel)/(8 * 1024 * 1024) = 3 MB
Copy the code

The image above is only 1024×768. If the image size is doubled, the size of the image in memory is increased by a square multiple, and the memory usage is still terrible after the number is increased.

In multi-image loading scenarios, iOS performs better no matter what it does in practice, but Android tends to mess up. Let’s take a closer look at how to optimize images on Android.

In some scenarios, Android will explode in memory, dropping the frame rate straight down to single digits. In this scenario, the small size Image container loads a very large Image. For example, a 100×100 container loads a 1000×1000 Image. The cause of memory explosion is the above mentioned reason.

So how do you solve this problem? Image has a resizeMethod attribute, which is to solve the problem of Android Image memory explosion. Decide what strategy to use to resize an image when the actual size of the image differs from the container style size.

  • resize:This property should be used in scenarios where small containers load large images. The principle is that before decoding the image, the algorithm will be used to modify its data in memory, the size of the image will be reduced to about 1/8 of the original image.
  • scale: Does not change the image byte size, but modifies the image width and height by scaling. Because of hardware acceleration, the load time is a little faster.
  • autoThe documentation says that the resize and scale attributes are automatically toggled by a heuristic algorithm.This heuristic algorithm is very misleadingAt first glance, it might seem like a different strategy for comparing container sizes and image sizes. But I looked at the source code, it is only a simple judgment of the image path, if it is a local image, it will use resize, other are scale attributes, so HTTP images are scale, we have to manually control according to the specific scene.

By the way, Android images also have an easy-in 300ms loading animation that looks slow when loading images. We can disable this loading animation by setting the fadeDuration property to 0.

2️ preferred images with 32 bit colour depth

📄 Color Depth wiki: github.com/DylanVann/r…

The concept of color depth has also been mentioned previously. For example, we often use PNG images with transparency, which are 32 bits:

  • R: Red, eight bits
  • G: Green, 8 bits
  • B: Blue, takes up 8 bits
  • A: Transparent channel, occupying 8 bits

Why do I recommend using 32-bit graphics? There are two direct causes:

  1. Android recommends using 🔗 ARGB_8888 for images because they look better
  2. The iOS GPU can load only 32-bit images. If it is a different format (say, a 24 bit JPG), it will be converted to 32 bits in the CPU and then transmitted to the GPU

32-bit graphics are recommended, but to be honest, this is out of control for front-end development because there are usually only two sources of graphics:

  1. The designer’s cut diagram, controlled by the designer
  2. Pictures on the Internet, controlled by the uploader

So if you want to optimize for this point, the cost of communication is very high, but the benefit is not high (generally only in the long list of some problems), but also an idea of picture optimization, so in this section.

3️ Image and ImageView are consistent in length and width

In order to reduce the size of images in memory, set resizeMethod={‘resize’}. If we can control the size of the images we load, we should keep the Image and ImageView the same length and width.

First let’s look at the problems caused by inconsistent width and length:

  • Image less than ImageView: the picture is not clear, the expression package electronic paste texture
  • Image larger than ImageView: waste memory, may cause OOM
  • Inconsistencies in size can lead to anti-aliasing calculations and increase the burden of graphics processing

When React Native was developed, the layout unit used was PT, which has a multiple relationship with PX. When load network Image, we can use the React of Native 🔗 PixelRatio. GetPixelSizeForLayoutSize method, according to the different load different images, the resolution of guarantee Image and ImageView aspect.

4 ️ ⃣ use react – native – fast – image

📄 react-native fast-image Document: github.com/DylanVann/r…

After the above analysis of Image attributes, the Image component is still weak in Image management. There is a react-native fast-image substitute for Image component in the community.

Its underlying use is 🔗 iOS SDWebImage and 🔗 Android Glide. These two star image download management library, native development students must be familiar with, in the cache management, load priority and memory optimization have a good performance. And these attributes are dual-platform available, the library is packaged, but the official website is only the installation and configuration of basic functions, if you want to introduce some features (such as support for WebP), or need to see the documentation of SDWebImage and Glide.

Before introducing React Native’s Android Image component, I would like to remind you that the base layer of the React Native Android Image component encapsulates FaceBook Fresco. Introducing this library is equivalent to introducing Glide, and the package size will inevitably grow, so it may need to be balanced before introducing it.

5️ image server assistant

The above mentioned images are optimized from the React Native side. However, a product never fights alone. In fact, it can save a lot of work with the help of the service side.

1. Using WebP

WebP advantage I do not need to say more, the same visual effect, the image volume will be significantly reduced. It also significantly reduces the size of CodePush hot update packages (where images take up more than 90% of the volume).

Although decompression of WebP may take a little bit more time on the front end, the overall benefit is good considering that the reduced transfer volume reduces network download time.

2. Map bed custom pictures

Generally larger enterprises have built-in graph bed and CDN services, will provide some custom picture functions, such as specifying the width and height of the picture, control the picture quality. Of course, some excellent third-party object storage also provides these functions, such as 🔗 qiuyun image processing.

With the cloud image customization function, the front end can easily control image attributes by controlling URL parameters.

For example, Android can change the size of image bytes through the resize of resizeMethod. Although this problem can be solved, this algorithm is still running in the front end and will occupy user memory resources. Let’s change the link to:

Long for 100 px/https://www.imagescloud.com/image.jpg/0/w/100/h/100/q/80: / / w/h: up to 100 px wide / / q: compression quality for 80Copy the code

This allows computing to be moved to the server side, reducing front-end CPU usage and optimizing front-end performance overall.


Separation of object creation calls

The separation of object creation and invocation is more of a coding habit.

We know that in JavaScript, everything is an object, but in the JS engine, creating an object takes about 10 times as long as calling an existing object. In most cases, the performance and time costs are trivial. But it’s worth summarizing here, because this habit of thinking is still important.

1️ public class fields syntax binding callback function

📄 document: zh-hans.reactjs.org/docs/handli…

As a front-end application, besides rendering the interface, another important thing is to handle user interaction and listen for various events. So binding various processing events to components is also an optimization point.

React is already a classic topic. I did a search and there have been articles about React since it first came out. There are always four or five solutions.

The most common binding method is to handle events directly through arrow functions:

class Button extends React.Component {
  handleClick() {
    console.log('this is:'.this);
  }

  render() {
    return <button onClick={(e)= > this.handleClick(e)}>Click me</button>; }}Copy the code

The problem with this syntax is that handleClick() is created every time the Button component is re-rendered. When re-render is performed a large number of times, the JS engine will be overloaded with garbage collection, causing performance problems.

🔗 recommends that developers use 🔗 public class fields syntax to handle callback functions, so that a function is created only once and the component re-render is not created again:

class Button extends React.Component {
  // This syntax ensures that handleClick's this is bound.
  handleClick = () = > {
    console.log('this is:'.this);
  }

  render() {
    return <button onClick={this.handleClick}>Click me</button>; }}Copy the code

In actual development, after some data comparison, the performance cost caused by different binding event modes is basically negligible, and the excessive number of re-render times is the performance killer. But I think there is an awareness of this, after all, it is logically unnecessary to re-render a new function at a time.

2️ ️ public class fields syntax binding rendering function

This is similar to the first one, except that the event callback is changed to render, which is common in React Native flatLists.

Many newcomers to Flatlist pass anonymous functions directly to the renderItem, so that a new anonymous function is created each time the Render function is called:

render(){
  <FlatList
    data={items}
    renderItem={({ item}) = > <Text>{item.title}</Text>} / >
}
Copy the code

This can be avoided by changing to a public class fields function:

renderItem = ({ item }) = > <Text>{item.title}</Text>;

render(){
  <FlatList
    data={items}
    renderItem={renderItem}
  />
}
Copy the code

In the same way, ListHeaderComponent and ListFooterComponent should be written with pre-rendered elements to avoid flickering when re-render is regenerated.

3️ ️ StyleSheet. Create replace StyleSheet. Flatten

📄 document: reactnative. Cn/docs/styles…

StyleSheet. Create, which converts the passed Object into an optimized StyleID, is optimized for memory usage and Bridge communication.

const styles = StyleSheet.create({
  item: {
    color: 'white',}});console.log(styles.item) // Prints an integer ID
Copy the code

In business development, we often pull out some common UI components and pass in different parameters to make the UI components behave differently.

For flexibility in UI styles, we typically use stylesheet. flatten, which combines the custom style passed through props and the default style into a style object:

const styles = StyleSheet.create({
  item: {
    color: 'white',}}); StyleSheet.flatten([styles.item, props.style])// <= merge default and custom styles
Copy the code

The advantage of this is that you can control the style flexibly, but the problem is that using this method will 🔗 recursively traverse the style object that has been converted to StyleID, and then generate a new style object. This would break the optimization prior to StyleSheet. Create, and might cause a performance burden.

Of course, this section does not mean that StyleSheet and flatten cannot be used. Versatility and high performance cannot be achieved at the same time. It is a positive solution to adopt different solutions according to different business scenarios.

4️ avoid creating new arrays/objects in the render function

In order to avoid passing undefined at [], we often pass an empty array by default:

render() {
  return <ListComponent listData={this.props.list| | []} / >
}
Copy the code

In fact, it is better to do the following:

const EMPTY_ARRAY = [];

render() {
    return <ListComponent listData={this.props.list || EMPTY_ARRAY} / >
}

Copy the code

This isn’t much of a performance optimization, but rather the same idea that I’ve highlighted many times before: object creation and call separation. After all, how much of a performance problem is it to recreate an empty array/object every time you render it?

Changing [] to the EMPTY_ARRAY constant is actually a programming habit, like avoiding Magic Number in everyday coding, but I think this optimization can fall under this category, so I mention it specifically.


5. Animation performance optimization

Smooth animation is easy, and on most devices, a 60fps frame rate is all you need. However, there are still some problems with React Native to achieve this goal. I drew a diagram that describes the current React Native infrastructure (version 0.61).

  • UI Thread: A Thread dedicated to drawing UI on iOS/Android
  • JS Thread: We write most of the business code on this Thread, React redraw, processing HTTP request results, disk data IO, etc
  • Other Thread: refers to other threads, such as data request threads, disk I/O threads, etc

As you can easily see, the JS thread is too busy and has too many things to do. In addition, UI Thread and JS Thread communicate with each other in an Async Bridge. As long as there are many other tasks, it is difficult to ensure that every frame is rendered in time.

The React Native animation optimization direction comes naturally:

  • Reduce asynchronous communication between JS threads and UI threads
  • Minimize JS Thread side computation

1 ️ ⃣ openuseNativeDrive: true

📄 File: facebook.github. IO /react-nativ…

JSON strings are used to pass messages between JS threads and UI threads. For some predictable animations, such as clicking a “like” button, a “like” animation will pop up, which is completely predictable, we can use useNativeDrive: True to enable the original animation drive.

By enabling the native driver, we send all the configuration information to the native end of the animation before it starts, using native code to execute the animation in the UI thread, rather than having to communicate back and forth between the two ends every frame. This way, the animation is completely disconnected from the JS thread at the beginning, so if the JS thread gets stuck at this point, the animation won’t be affected.

UseNativeDrive: True is easy to use, just add useNativeDrive: True to the animation configuration before the animation starts:

Animated.timing(this.state.animatedValue, {
  toValue: 1.duration: 500.useNativeDriver: true // <-- plus this line
}).start();
Copy the code

When enabled, all animations will run in the Native thread and the animation will be very smooth and silky.

After a variety of violence tests, the use of native drive animation, basically no frame phenomenon, but with JS drive animation, once the operation speed is accelerated, there will be frame phenomenon.

It should be noted that the useNativeDriver property also has limitations. It can only be used for non-layout-related animation properties, such as transform and opacity. Layout related properties, such as height and position, display an error when enabled. Besides, as mentioned above, useNativeDriver can only be used in predictable animations, such as following gesture animations, which cannot be used by useNativeDriver.

2 ️ ⃣ usesetNativeProps

📄 File: facebook.github. IO /react-nativ…

The setNativeProps property is equivalent to manipulating the BROWSER DOM directly. React Native does not recommend DOM manipulation directly. However, business scenarios are constantly changing, and DOM manipulation is inevitable in some situations. The same is true in React Native.

For example, in the following GIF, when scrolling up and down the screen, the offset on the Y-axis can be optimized by enabling useNativeDrive: true with the ScrollView#onScroll property. But you can see that as you slide up and down, the numbers in the circle change.

If you store numbers in this.state, each slide will inevitably require a lot of setState, and the React side will do a lot of redrawing, which may cause frames to drop. Here we can use setNativeProps to avoid redrawing on the React side, which is equivalent to directly modifying the DOM numbers to make the animation more fluid.

3 ️ InteractionManager ⃣ use

📄 File: facebook.github. IO /react-nativ…

One of the reasons native apps feel so smooth is that they avoid heavy lifting during interactions and animations.

In the React Native JS thread is too busy, with nothing to do, we can put some arduous task on InteractionManager. RunAfterInteractions (), to ensure the execution of all interactions and animation has finished processing.

InteractionManager.runAfterInteractions(() = > {
  / /... Tasks that need to be synchronized for a long time...
});
Copy the code

Among React Native’s components, PanResponder, Animated, and VirtualizedList all use InteractionManager to balance execution timing between complex tasks and interactive animations.

4️ use of react-native reanimated and react-native Gear-handler

📺 video tutorial: www.youtube.com/channel/UC8…

📄 react-native Gesture-handler Documentation: github.com/software-ma…

📄 react-native reanimated: github.com/software-ma…

These two libraries were created by 🔗 William Candillon, a free software developer on Youtube. Check them out later and they are also the default built-in animation and gesture libraries for Expo.

These libraries are intended to replace the 🔗 and 🔗 libraries provided by React Native. In addition to a more friendly API, I think the biggest advantage is that gesture animations run on the UI Thread.

As we mentioned earlier, useNativeDrive: True can only be used for predictable animations. Animation that follows gestures cannot use this property, so gesture capture and animation are computed dynamically on the JS side.

Let’s take a simple example: the ball follows a gesture.

We can see that the JS Thread has a lot of calculations, and then the calculation results are asynchronously transmitted to the UI Thread. If there is a slight change, the frame will drop.

If react-native Gesture-handler is used, gesture capture and animation are performed by UI threads, and the fluency is greatly improved without JS Thread computation and asynchronous Thread communication:

So if you want to build complex gesture animations with React Native, use react-native Gesture-handler and react-native Reanimated, which can greatly improve animation fluency.

5 ️ BindingX ⃣ use

📄 BindingX document: alibaba. Making. IO/BindingX/gu…

BindingX is an open source framework of Ali, which is used to solve the problem of rich interaction on WEEx and React Native. The core idea is to describe the “interaction behavior” in expressions and preset to Native in advance, so as to avoid frequent communication between JS and Native when the behavior is triggered.


Of course, there will certainly be some learning costs associated with the introduction of the above third-party libraries. For complex interactive pages, some teams may use native components instead. For example, 🔗 Meituan takeout uses native components to realize fine animation and strong interactive modules. Therefore, the specific use depends on the technical reserves and APP scenes of the team.


Long list performance optimization

In React Native development, the easiest scenario to encounter that requires a certain amount of performance is long lists. In daily business practice, after optimization, thousands of data rendering or no problem.

The virtual list front end has always been a classic topic, and the core idea is simple: only render current and upcoming views, and render distant views with blank views to reduce the memory footprint of long lists.

On the React Native website, the 🔗 list of configuration optimizations is a good idea. Basically, you only need to know a few configuration items and then flexibly configure them. But the problem is in the “understand clearly” these four words, this section I will combine pictures and texts, to tell you clearly these several configurations.

1️ relations between various lists

React Native has several list components. Here are some of them:

  • ScrollView: Will render all views in the View, directly connected to the Native scroll list
  • VirtualizedList: Virtual list core file, using ScrollView, long list optimization configuration items mainly control it
  • FlatList: Use VirtualizedList to implement one-line, multi-column functions. Most of the functions are provided by VirtualizedList
  • SectionList: Use VirtualizedList and the underlying use VirtualizedSectionList to convert two-dimensional data into one-dimensional data

There are also some other dependency files, there is a good summary of 🔗 blog, I borrow its diagram here:

We can see that VirtualizedList is the main actor. Let’s examine its configuration items with some sample code.

2️ retail list configuration item

I’ll write a little demo before I do that. The demo is very simple, a list of even-odd rows in different colors based on the FlatList.

export default class App extends React.Component {
  renderItem = item= > {
    return (
      <Text
        style={{
            backgroundColor: item.index % 2= = =0 ? 'green' : 'blue'}} >{' item '+ (item.index + 1) +' '}</Text>
    );
  }

  render() {
    let data = [];
    for (let i = 0; i < 1000; i++) {
        data.push({key: i});
    }

    return (
      <View style={{flex: 1}} >
        <FlatList
            data={data}
            renderItem={this.renderItem}
            initialNumToRender={3}// The number of elements to render firstwindowSize={3}// Render area heightremoveClippedSubviews={Platform.OS= = ='android'} // Whether to crop the subviewmaxToRenderPerBatch={10}// Increment the maximum number of rendersupdateCellsBatchingPeriod={50}// Incremental render intervaldebug/ / opendebugPatterns / >
      </View>); }}Copy the code

VirtualizedList has a debug configuration item. After this item is enabled, the virtual list is displayed on the right side of the view.

InitialNumToRender, windowSize, Viewport, Blank Areas and other concepts can be intuitive to learn. InitialNumToRender, windowSize, Viewport, Blank areas, etc.

The following is a screenshot of the demo after debug is enabled:

The above figure is still very clear. The yellow part of the debug indicator bar on the right side represents the Item in memory. Let’s describe each attribute in words again:

1.initialNumToRender

The first number of elements should be rendered, preferably just covering the first screen. And as you can see from the Debug indicator, this batch of elements will always be in memory.

2.Viewport

Viewport height, where the user can see the content, is generally the height of the device.

3.windowSize

Render area height, usually an integer multiple of Viewport. Here I set it to 3, and as you can see from the Debug indicator, it is 3 times the height of Viewport, extending by 1 screen height above and 1 screen height below. Everything in this area is stored in memory.

Setting windowSize to a smaller value can reduce memory consumption and improve performance, but when scrolling through the list quickly, the chances of encountering unrendered content increase and you will see a white View filled with space. You can test it out by setting windowSize to 1, and you’ll see a placeholder View 100% of the time.

4.Blank areas

Empty View, VirtualizedList replaces items outside the rendered area with a blank View to reduce the memory footprint of long lists. You can have either the top or the bottom.

The react Virtual DOM (I set initialNumToRender and windowSize to 1 for screenshots) is the same as the renderer above.

5.removeClippedSubviews

This property translates as “clipping subview”, but the documentation is not very clear. The general idea is that setting it to true can improve rendering speed, but it can be buggy on iOS. The VirtualizedList property is passed directly through the ScrollView without any optimization.

FlatList is enabled by default on Android during a 🔗 commit for version 0.59. If your version is earlier than 0.59, you can enable it in the following ways:

removeClippedSubviews={Platform.OS === 'android'}
Copy the code

6. MaxToRenderPerBatch and updateCellsBatchingPeriod

The VirtualizedList data is not rendered all at once, but in batches. These two properties control incremental rendering.

These two properties is generally cooperate with, maxToRenderPerBatch said each time the maximum number of incremental rendering, updateCellsBatchingPeriod said each incremental rendering time interval.

We can adjust these two parameters to balance rendering speed and response speed. However, as a metaphysical science, it is difficult to come up with a unified “best practice” for tuning, so we leave these two attributes alone in our business and use the default values.

2 ️ ⃣ ️ ListLtems optimization

📄 ListLtems optimization document: reactnative. Cn/docs/optimi…

There are several optimizations mentioned in the document, which I have already introduced in the previous section. Here are a few more:

1. Use getItemLayout

If the FlatList (VirtualizedList) has a fixed ListLtem height, then using getItemLayout is very economical.

In the source code (#L1287, #L2046), if you do not use getItemLayout, then all Cell height, call the View onLayout dynamic calculation of height, this operation is time-consuming; If we use getItemLayout, the VirtualizedList will directly know the height and offset of the Cell, eliminating calculation and saving overhead.

Here I also want to mention a few points, I hope you use getItemLayout to pay more attention to:

  • If ListItem height is not fixed, use getItemLayout to return a fixed height, because the final render height is not the predicted height, the page will jump [🔗 problem link]
  • If you useItemSeparatorComponent, the size of the dividing line should also be taken into account in the calculation of offset [🔗 Document link】
  • If the FlatList is used when usedListHeaderComponentThe size of the Header should also be taken into account in the calculation of the offset.🔗 official sample code link】

2.Use simple components & Use light components

Using simple components, the core is to reduce logical judgment and nesting, optimization can refer to the “two, reduce rendering pressure” content.

3.Use shouldComponentUpdate

Refer to the content of “one, re-render”.

4.Use cached optimized images

Refer to “three, picture optimization those things” content.

5.Use keyExtractor or key

For general optimizations, see the React documentation 🔗 listing & Key.

6.Avoid anonymous function on renderItem

RenderItem avoids the use of anonymous functions. See “Separation of object creation calls.”


React Native performance optimization tools

Performance tuning tools are essentially a subset of debugging tools. Due to its particularity, React Native requires tools from RN, iOS, and Android to perform performance analysis and debugging. Below, I will list the tools I usually use. Specific usage methods are not the focus of this paper.

1.React Native Official debugging tool

This official website said very clear, specific content can be seen 🔗 direct link.

2.react-devtools

React Native runs on a Native APP. You can’t use a browser plugin to view the layout, so use the Electron based React – devTools. React Native is still 0.61 as of this writing, and the latest V4 version of React – DevTools is not supported. See the 🔗 link for detailed installation methods.

3.XCode

IOS develops ides that use Instruments and Profiler for debugging when viewing and analyzing performance problems.

4.Android Studio

Android development IDE, check performance if you can use the Android Profiler, 🔗 official website written very detailed.

5.iOS Simulator

IOS emulator, its Debug can see some analysis content.

6.Android Real -> Developer options

Android developer options have a lot to offer, such as GPU rendering analysis and animation debugging. Real machine debugging can be opened with the use.


Recommended reading

React Native Performance Optimization Guide: React Native Performance Optimization Guide: React Native

The full text refers to nearly 50 links, all put at the end of the text is too much space, so I have scattered in the article everywhere, I use emoji 🔗 mark way to prompt, you can go to the original text to see where there is doubt.

I would also like to recommend my previous articles on Webpack, both of which are original to the web:

  • This article introduces the 5 most confusing points in Webpack, nuggets 700 likes, and explains the concepts that look alike but mean different things
  • A detailed introduction to what webpack DLL is, and gives two best practices, get rid of cumbersome DLL configuration
  • We are all familiar with the HTTP protocol, but what about the holes written into the HTTP specification? Take a look at the dark holes in HTTP


Finally, I would like to recommend my personal public account, “Lu Egg Lab”. I usually share some front-end technology and data analysis content. If you are interested, you can follow a wave of ❤️