Original: libo
About the frame
Now that I’ve learned a little bit about Rn recently, I’m going to briefly discuss two or three things about RN’s performance.
First of all, before discussing the performance of mobile terminal, it is necessary to understand the concept of frame, frame rate is a very important criterion to judge the performance of mobile terminal application. As we all know, whether it’s a cell phone or a computer or a video, it’s a set of static pictures that change rapidly at a steady rate. We call each image in this group a frame, and the number of frames per second displayed directly affects the smoothness and realism of the user interface. Using iOS for example, iOS devices offer a frame rate of 60 frames per second, which leaves the developer and UI system around 16.67ms to do all the work needed to generate a single still image (frame). If this is not done within the allocated 16.67ms, frames will be lost and the interface will not behave smoothly. However, it is often difficult to maintain a refresh rate of 60 frames per second for an application in use, and both native code and RN cannot avoid additional unnecessary performance overhead, so human intervention is necessary.
Before we discuss how RN can be optimized for performance, let’s discuss where rn’s performance problems can occur.
The principle of RN
First of all, RN provides a JS running environment for us on the mobile end, so front-end developers only need to care about how to write JS code and draw UI into the Virtual DOM, not the specific platform.
RN and H5 pages have essential differences when running JS, which enables RN to have the appearance and feel similar to native App and far surpass H5 in terms of experience. RN does a lot of the dirty work of converting our JS code into native code execution, which means RN pages are actually rendered as native components, not as webViews.
The essence of RN is to build the Bridge in the middle so that JS and native can call each other. 
About the thread
In a native iOS app, unlike a single js thread, a native app can asynchronously execute network requests or other operations unrelated to UI rendering in the child thread to increase the smoothness of the main thread and make the main thread focus on view rendering as much as possible. This is the basic concept of native application performance optimization.
In rn applications, rn provides a special thread, the JS thread, for executing front-end code. Therefore, there are usually the following threads in RN applications:
- Main thread: UI thread, view rendering, handling user interaction
- Sub-threads: process network requests and perform time-consuming operations
- Js thread: Runs JS code. For most React Native applications, the business logic runs on a JavaScript thread.
If you only write JS, then the development process is not exposed to the above two threads. However, we need to understand that during the operation of RN application, JS thread and main thread work in a highly collaborative way. For example, when the user triggers the UI event response, the main thread will receive the event, pass it to JS code, and process the event in JS thread. When UI update is initiated at the JS end, data and UI structure will be synchronized to the native end at the same time, and update rendering will be completed in the main thread.
Updating data to natively-supported views is done in batches and is sent to the native end each time the event loop runs, usually before the end of a frame (if all goes well). If the JavaScript thread does not respond to a frame in time, or the main thread is still busy rendering the previous frame, a frame loss is considered to have occurred. For example, if this. SetState is called on the root component of a complex application, resulting in an expensive redraw of the subcomponent tree, it may take a long time, such as 200ms or 12 frames lost. At this point, any javascript-controlled animation will get stuck. As long as the lag exceeds 100ms, the user will feel it clearly.
This often happens during a Navigator switch: when we push a new route, JavaScript needs to draw all the components needed for the new scene to send the correct commands to the native side to create the view. Because switching is controlled by JavaScript threads, it often takes several frames, causing some stuttering. Sometimes components do extra things in the componentDidMount function, which can even cause page transitions to stall for up to a second.
Another example is the response to touch events: if you work across multiple frames in the button response method, you may cause the button’s own click animation to be delayed (color or transparency changes). This is because the JavaScript thread is too busy to handle the raw touch events sent by the main thread. As a result, there is no time to respond to these events and command the main thread’s page to adjust color or transparency.
From the above examples, it is not difficult to see that performance is affected because of the redraw. Native apps like Navigator never need to pay attention to lag. So what makes switching from Rn Navigator so slow?
First of all, the running speed of native code on the device is unquestionable, while JS, as a scripting language, is known for its fast speed, that is to say, both sides run quickly independently. In this case, the performance bottleneck will only appear in the communication between the two sides, but the two sides do not communicate directly, but act as a middleman through Bridge. The operation logic of finding and calling modules, interfaces, and so on can create a lag that the UI layer can clearly perceive, which means that the Bridge’s transformation during the drawing process is inefficient. We can see that in an example, when we scroll up and down a ScrollView, no matter how busy the JavaScript thread is, or even if the JavaScript thread gets stuck, the ScrollView doesn’t slide smoothly. Because ScrollView runs and slides are completely on top of the main thread.
However, ScrollView’s scroll events are distributed to the JS thread, and if we received every scroll event and triggered a UI update with every scroll, the application would be disastrous. Each UI update not only means a JS redraw, but also triggers a code reload of the bridge, and the generation and rendering of the UI view after the main thread receives the correct instructions. Obviously, performance control becomes the logic of how to minimize Bridges. The application requires Bridge work in three cases.
- UI event response: This content occurs in Native terminal and is passed to JS terminal as an event. It is just a trigger and does not have excessive performance problems
- UI update: JS determines what interface to display and how to style the page. Generally, UI update is initiated by JS terminal, and a large amount of data and UI structure is synchronized to native terminal. Performance problems often occur in such update, especially in the case of complex interface, large amount of data change, complex animation and high frequency of change.
- UI event response +UI update: If the UI update doesn’t change much, then it’s not a problem. If UI events trigger UI updates, and the logic is complex and time-consuming, data synchronization between the JS end and Native end may take time, which may cause performance problems.
About how to optimize
Here are some of the more common functions and methods.
ShouldComponentUpdate and pureComponent:
The react function shouldComponentUpdate is called before render(). Its two parameters, nextProps and nextState, should render(). Represents the value of the next props and state, respectively. We override this hook to prevent subsequent render() calls and component rerendering when the function returns false, or to render the component downward when the function returns true.
For example:
shouldComponentUpdate(nextProps, nextState) { return nextState.b ! == this.state.b }Copy the code
When the current value of the props or state is inconsistent, we will render to achieve the optimization effect.
Or with pureComponent, custom stateful components can be inherited as much as possible from Pure Component rather than component. After reading the official description, you can see that after all, it’s just a normal Component that automatically uses the shouldComponentUpdate hook. If the component inherits from pureComponent, shouldComponentUpdate is no longer necessary.
2.InteractionManager: InteractionManager is essentially a delay scheduling function that automatically schedules some of the longer work until all the interactions or animations are complete. This ensures smooth running of JavaScript animations. Apply this to schedule a task to be executed after the interaction and animation are complete:
InteractionManager.runAfterInteractions(() => { // ... Tasks that take a long time to synchronize... });Copy the code
3. SetNativeProps: The setNativeProps method can be understood as a direct dom modification of the Web. Use this method to modify the View, the Text, such as an RN own components, will not trigger a component componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate component method of life cycle.
This does provide some performance gains, but also makes the code logic difficult to understand, and does not solve the problem of data synchronization overhead from the JS side to the Native side. The setState() and shouldComponentUpdate() methods are recommended instead.
4. LayoutAnimation: The Animated interface is typically Animated, but the Animated interface calculates every keyframe in the JavaScript thread. LayoutAnimation uses the native Animation library, Core Animation, LayoutAnimation will pass the parameters of the animation to Native at one time, and then native will complete the animation. The JS thread will not participate in the animation process any more. LayoutAnimation, however, only works on one-off animations, for example if the animation is likely to be cancelled mid-animation, then Animated is still needed.
5. Use less stateless components and use stateless components as much as possible. Stateless components will not be instantiated when converted into native view, which can improve performance.