preface
Recently, I have been reconstructing RN projects. By consulting various materials and starting from the bottom of RN, I have thought and summarized some performance optimization problems from React to React-Native
Performance · React Native requires Unbundling + inline requires Performance · React Native requires Unbundling + inline requires Performance · React Native requires Unbundling + inline requires Performance · React Native requires Unbundling + inline requires
Let’s take a look at some of the common causes of performance problems
Here I will give my own conclusions, then I will understand why I do this from the underlying principles, and finally I will expand each method in detail (to be continued).
This part is not dead knowledge, maybe one day I will have a broader idea and solution, may overturn the current conclusion, so this article will continue to be updated…
Overview of RN performance optimization
Before we talk about performance, let’s look at how RN works
Through RN, we can use JS to realize cross-platform App, which is also called “Write once, run everywhere” by FB
RN provides us with the JS runtime environment, so front-end developers only need to care about how to write JS code, draw UI only into the Virtual DOM, do not need to care about the specific platform
As for the dirty work of converting JS code into native code, the bottom layer of RN is all done
The essence of RN is to build the Bridge in the middle so that JS and native can call each other
The loading process of RN is mainly divided into several stages
- Initialize the RN environment
- Create a Bridge,
- JS environment in Bridge
- RN modules and UI components
- Download the JS Bundle
- Run JS Bundle
- To render the page
Dive into React Native performance | Engineering Blog | Facebook Code | Facebook
From the performance test of FaceBook ios version, it can be seen that the green JS Init + Require occupies most of the time, which is mainly to initialize the JS environment: download the JS Bundle and run the JS Bundle
JS Bundle is a JS file packaged by RN development tools, which not only contains THE JS codes of RN page components, but also the JS codes of React and react-native, as well as the codes of Redux and React-Navigation that we often use. RN’s very simple demo page Minify has a JS Bundle file size of nearly 700KB, so the JS Bundle file size is a bottleneck for performance optimization
Assume we have a large App, it contains a lot of pages, but in the routine use of many pages will not be open, even there are some complex configuration file and rarely use function, the related code, the App startup is not needed, so we can consider to optimize performance by Unbundling unpacking
As for how to reduce the size of the Bundle, the current mainstream method is to split the Bundle and separate the framework code from the business code. The framework code is very large, so it is separated and pre-loaded separately, while the business code is distributed separately as a small JS code
But before unpacking, FB official also mentioned several optimization points that should be done before this
Doing less
- Cleanup Require/Babel helpers
- Avoid copying and decoding strings when loading the bundle
- Stripping DEV-only modules
Scheduling
- Lazy requires
- Relay incremental cache read
- De-batching bridge calls, batch Relay calls
- Early UI flushing
- Lazy native modules loading
- Lazy touch bindings on text components
React-native universal construction and performance optimization – Web front-end Tencent IVWeb team community is worthy of Tencent, Mainly talked about the construction of generalized, bundle of local subcontracting, project online performance analysis several bundles of RN subcontracting transformation RN pack those thing | YMFE React Native unpacking and hot update plan Solartisan
Speaking of Unbundling, the official documentation also requires inline and an analysis
Inline requires delay the requiring of a module or file until that file is actually needed. Inline requires delay the requiring of a module or file until that file is actually needed. Until you really need them
It’s easy to see if you look at a small example
import React, { Component } from 'react'; import { Text } from 'react-native'; / /... import some very expensive modules // You may want to log at the file level to verify when this is happening console.log('VeryExpensive component loaded'); export default class VeryExpensive extends Component { // lots and lots of code render() { return <Text>Very Expensive Component</Text>; }}Copy the code
import React, { Component } from 'react'; import { TouchableOpacity, View, Text } from 'react-native'; // Set the component to null let VeryExpensive = null; export default class Optimized extends Component { state = { needsExpensive: false }; DidPress = () => {if (VeryExpensive == null) {// If (VeryExpensive == null) {VeryExpensive = require('./VeryExpensive').default; } this.setState(() => ({ needsExpensive: true, })); }; render() { return ( <View style={{ marginTop: 20}}> <TouchableOpacity onPress={this.didPress}> <Text>Load</Text> </TouchableOpacity> // Decide whether to render the component as required {this.state.needsExpensive ? <VeryExpensive /> : null} </View> ); }}Copy the code
Even without unbundling inline requires can lead to startup time improvements, because the code within VeryExpensive.js will only execute once it is required for the first time
The above content is mainly about the performance optimization of the first screen rendering speed
So what are the performance points once you enter the App? Let’s go back to Bridge
First of all, under the aura of apple and Google two bosses, native code on the device running speed, no doubt, and JS as a scripting language, is famous for its fast namely originally, that is to say, on both sides of the independent running quickly, so it seems, performance bottlenecks will only appear on each end of the communication, but both sides is not the direct communication, Instead, the Bridge acts as a middleman, finding and invoking operational logic, such as modules and interfaces, to the point where the UI layer can clearly perceive the lag, and performance control becomes how to minimize the logic needed by the Bridge.
- UI event response occurs in Native terminal and is transmitted to JS terminal in the form of events. It is only a trigger and will 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 THE JS end, and a large amount of data and UI structure is synchronized to the native end. Performance problems often occur in such update, especially in the case of complex interface, large amount of data change, complex animation and high change frequency
- UI event response +UI update If UI update does not change much, the problem is not big. If UI events trigger UI update, and the logic is complex and time-consuming, data synchronization between THE JS end and Native end may take a time difference, resulting in performance problems
In summary, the core RN performance optimization points are fairly clear
- First screen rendering optimization: deal with JS Bundle size, file compression, caching
- UI update optimization
- Reduce updates or merge multiple updates
- Improve component response speed:
- SetNativeProps update Native component properties directly at the bottom.
- Perform the update callback immediately
- Animation optimization
- By using the Annimated class library, the updates are sent to the Native end at one time, and the Native end is responsible for the updates
- Save some time-consuming actions for animation and UI updates
- Other optimizations (code level)
Each dot will mainly be written in the order that is easy to implement
shouldComponentUpdate
Life cycle see the official documentation react.component-react
Changes to the state and props in react applications cause re-render
Consider the following case
class Home extends Component<Props> { constructor(props) { super(props); This. State = {a: 'I see will not re - render,}} to render () {the console. The log (' to render the re - render -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --'); return ( <View style={styles.container}> <TouchableOpacity style={styles.addBtn} onPress={() => this.setState({ a: this.state.a })}> <Text>{this.state.a}</Text> </TouchableOpacity> </View> ); }}Copy the code
The core code isthis.setState({ a: this.state.a })
It’s not changing a, it’s just setState, it’s rerendering. Imagine the performance waste if the page has a lot of data
How about adding the shouldComponentUpdate hook
shouldComponentUpdate(nextProps, nextState) { return nextState.a ! == this.state.a }Copy the code
Well, that’s better. No more mindless rendering
What if it’s a reference object?
const obj = { num: 1 }; class Home extends Component<Props> { constructor(props) { super(props); this.state = { b: null } } componentWillMount() { this.setState({ b: Obj})} render () {the console. The log (' to render the re - render -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- '); return ( <View style={styles.container}> <TouchableOpacity style={styles.addBtn} onPress={() => { obj.num++; this.setState({ b: obj }) }}> <Text>{this.state.b.num}</Text> </TouchableOpacity> </View> ); }}Copy the code
B always points to the same reference object obj, although obj.num is changed every time it is clicked
But will the page be re-rendered?
Continue to look at the picture
Good, the contents of the object are changed and the page is re-rendered
How about adding shouldComponentUpdate?
shouldComponentUpdate(nextProps, nextState) { return nextState.b ! == this.state.b }Copy the code
ShouldComponentUpdate () should only make a shallow comparison and return false, the page should not be rerendered
ShouldComponentUpdate should be a good explanation of its features
So how do you handle the case of referring to objects? By far the most popular method is to use immutableJS, facebook’s own version of GitHub/immutablejs
React top-level api-react
React.PureComponent React.PureComponent is similar to React.Component. The difference between them is that React.Com ponent doesn ‘t implement shouldComponentUpdate (), but React.PureComponent implements it with a shallow prop and state comparison.
If your React component’s render() function renders the same result given the same props and state, you can use React.PureComponent for a performance boost in some cases.
Note
PureComponent’s shouldComponentUpdate() only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences. Only extend PureComponent when you expect to have simple props and state, or use forceUpdate() when you know deep data structures have changed. Or, consider using immutable objects to facilitate fast comparisons of nested data.
Furthermore, PureComponent’s shouldComponentUpdate() skips prop updates for the whole component subtree. Make sure all the Children components are also “pure”.
At the end of the day, it’s just automaticshouldComponentUpdate
Hook Component, nothing special
Component response speed (InteractionManager, requestAnimationFrame, setNativeProps)
1) InteractionManager
InteractionManager and requestAnimationFrame(FN) serve a similar purpose in order to avoid animation stalling if the animation is executed while rendering, or if a large number of code calculations are blocking the page process. InteractionManager runAfterInteractions is executed after the end of the animation or operation
InteractionManager.runAfterInteractions(() => { // ... long-running synchronous task... });Copy the code
2) requestAnimationFrame
Window. | MDN requestAnimationFrame – Web API interface using requestAnimationFrame (fn) in the next frame immediately implement the callback, so that it can be asynchronous to improve the response speed of the components;
OnPress() { this.requestAnimationFrame(() => { // ... SetState operation}); }Copy the code
SetImmediate /setTimeout(): This is a primitive trick that most likely affects the flow of an animation
3) setNativeProps
Direct Manipulation · React Native updates the properties of Native components directly at the bottom by means of Direct Manipulation, thus avoiding the large overhead caused by rendering component structures and synchronizing too many view changes.
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.
Use setNativeProps when frequent re-rendering creates a performance bottleneck Direct manipulation will not be a tool that you reach for frequently; you will typically only be using it for creating continuous animations to avoid the overhead of rendering the component hierarchy and reconciling many views. setNativeProps is imperative and stores state in the native layer (DOM, UIView, etc.) and not within your React components, which makes your code more difficult to reason about. Before you use it, try to solve your problem with setState and shouldComponentUpdate.
Three, animation,
Animated’s premise is to minimize unnecessary animation, and for details on how Animated Animated can be Animated, see the official documentation for Animated · React Native
If Animated is difficult to calculate, such as folding, adding and reducing views, resizing, etc., then you can use LayoutAnimation to smoothly perform a one-time animation and see how the animation compares with setState
directlysetState
LayoutAnimation
Results 1
LayoutAnimation
Results 2
It’s very simple to use, and there are two cases
- Use the default effect
在componentWillUpdate
Inside the hook, make the entire component all animations should have this effect, or in the case that requires animation alonesetState
Method is used beforeLayoutAnimation.spring();
componentWillUpdate() {
// spring, easeInEaseOut, linear
LayoutAnimation.linear();
}Copy the code
- Use custom effects
componentWillUpdate() {
LayoutAnimation.configureNext(config)
}Copy the code
Const config = {duration: 500, / / animation time create: {/ / spring, linear, easeInEaseOut easeIn, easeOut, the rid_device_info_keyboard type: LayoutAnimation. Types. Linear, / / opacity, scaleXY transparency, displacement property: LayoutAnimation. Properties. The opacity,}, the update: {/ / update display animation type: LayoutAnimation. Types. EaseInEaseOut,}};Copy the code
(To be continued…)
Afterword.
Thank you for your patience to see here, hope to gain!
If you are not very busy, please click star⭐ [Github blog portal], which is a great encouragement to the author.
I like to keep records in the learning process and share my accumulation and thinking on the way to the front end. I hope to communicate with you and make progress together.