6 min read

Since React Native[1] came out, the core group of developers, as well as the community, kept on improving its framework, including the performance and stability of the technology. In this article, we talk about React Native’s performance. This blog post is aimed at those people who want to learn more about React Native, but it might be a bit too complex for beginners.

How does it work?

In order to understand how to optimize our application, we have to understand how React Native works. But don’t worry; it’s not too hard.

Let’s take the following piece of code into consideration:

st=>start: Javascript
e=>end: Natif
op=>operation: Bridge

st->op->e

Let’s discuss what it represents. React Native bases itself on two environments: a JS (Javascript) environment and a Native environment. These two entities communicate together with a bridge.

  • The JS is our “organizer.” This is where we will run our algorithms, moderate our views, run network calls, and so on.
  • The Native is there for the display and the physical link part. It senses physical events as well as some virtuals if we ask it to do so, and then sends it to the JS part.
  • The bridge exists as a link, as shown in the following code:
      render(): ReactElement<*> {
    return (
<TextInput
          value={''}
          onChangeText={() => { /* Here we handle native event */ } />
    );  
}

Here, we simply have a Text Input. Most of the component involves all the branches of the React Native stack. This Text Input is called in JS, but is displayed on the device in Native. Every character typed in the Native component involves the physical event, transforms it in letter or action, and then transmits it by the bridge to the JS component.

In all of the transactions of data between JS and Native, the Bridge always intervenes so that the data is included in both parts. The bridge has to serialize the data.

The bridge is simple. It’s a bit stupid, and it has only one job, but… it is the one that will bother us the most.

The Bridge and other losses of performance

Bridge

Imagine that you are in an airport. You get your ticket online in five minutes; you are already in the plane and the flight will take the time that it’s supposed to. However, before that, there’s the regulation of the flight—the checking in. It will take horribly long to find the right flight, drop-down your luggage at the right place, go through security and get yourself checked, and so on.

Well, this is our Bridge.

Js is fast even though it is the main thread. Native is also fast, but the Bridge is slow. Actually, it’s more like it has so much data to serialize that it takes it so much time to serialize that he can’t improve its performance.

Or… It is slow, simply because you made it go slow!

The Bridge is optimized to batch the data[2]. Therefore, we can’t send it data too fast; and, if we really have to, then we have to minimize to the maximum.

Let’s take for example an animation. We want to make a square go from left to the right in 10 seconds.

  • The pure JS versions:
      /* on top of class */
let i = 0;
loadTimer = () => {
    if (i < 100) {
        i += 1;
        setTimeout(loadTimer, 100);
    }
};

...
componentDidMount(): void {
    loadTimer();
}
...
render(): ReactElement<*> {
    let animationStyle = {
        transform: [
            {
                translateX: i * Dimensions.get('window').width / 100,
            },
        ],
    };
    return (
<View
            style={[animationStyle, { height: 50, width: 50, backgroundColor: 'red' }] }
        />
    );
}

Here is an implementation in pure JS of a pseudo animation. This version, where we make raw data go through the bridge, is dirty. It’s dirty code and very slow, TO BAN!

  • Animated Version:
          ...
    componentDidMount(): void {
        this.state.animatedValue.setValue(0);
        Animated.spring(this.state.animatedValue, {
            toValue: 1,
        });
    }
    ...
    render(): ReactElement<*> {
        let animationStyle = {
            transform: [
                {
                    translateX: this.state.animatedValue.interpolate({
                        inputRange: [0, 1],
                        outputRange: [0, Dimensions.get('window').width],
                    }),
                },
            ],
        };
        return (
    <Animated.View
                style={[animationStyle, { height: 50, width: 50, backgroundColor: 'red' }] }
            />
        );
    }

It’s already much more understandable. The Animated library has been created to improve the performance of the animations, and its objective is to lighten the use of the bridge by sending predictions of the data to the native before starting the animation.

The animation will be much softer and successful with the rightful library. The general perfomance of the app will automatically be improved. However, the animation is not the only one at fault here. You have to take time to verify that you don’t have too much unnecessary data going through the bridge.

Other Factors

Thankfully, the Bridge isn’t the only one at fault, and there are many other ways to optimize a React Native application.Therefore, here is an exhaustive list of why and/or how you can optimize your application:

  • Do not neglect your business logic; even if JS and native are supposed to be fast; you have to optimize them.
  • Ban the while or synchronize functions, which takes time in your application. Blocking the JS is the same as blocking the application.
  • The rendering of a view is costly, and it is done most of the time without anything changing! It’s why you MUST use the ‘shouldComponentUpdate’ method in your components.
  • If you do not manage to optimize a JavaScript component, then it means that it would be good to transduce it in Native. The transactions with the bridge should be minimized.
  • There are many states in a React Native application. A debug stage, which is a release state. The release state increases the performance of the app greatly with the flags of compilation while taking out the dev mode. On the other hand, it doesn’t solve everything.
  • The ‘debugger mode’ will slow down your application because the JS will turn on your browser and won’t do it on your phone.

Tools

The React Native “tooling” is not yet very developed, but a great part of the toolset is that itis coming from the application. A hundred percentof the functionality is native. Here is a short list of some of the important tools that should help you out:

Tools

Platform

Description

react-addons-perf Both This is a tool that allows simple benchmarks to render components; it also gives the wasted time (the time loss to give the components), which didn’t change.
Systrace Android This is hard to use but useful to detect big bottlenecks.
Xcode iOS This function of Xcode allows you to understand how our application is rendered (great to use if you have unnecessary views).
rn-snoopy Both Snoopy is a softwarethatallows you to spy on the bridge. The main utility of this tool is the debug, but it can be used in order to optimize.

You now have some more tricks and tools to optimize your React Native application. However,there is no hidden recipeor magic potion…It will take some time and research.

The performance of a React Native application is very important. The joy of creating a mobile application in JavaScript must at least be equal to the experience of the user testing it.

About the Author

Pierre Monge is an IT student from Bordeaux, France. His interests include C, JS, Node, React, React Native, and more. He can be found on GitHub @azendoo.

[1]React Native allows you to build mobile apps using only JavaScript. It uses the same design as React, allowing you to compose a rich mobile UI from declarative components.

[2]Batch processing

LEAVE A REPLY

Please enter your comment!
Please enter your name here