React+redux Rendering Performance Optimization principle

Source: Internet
Author: User

Everyone knows that one of the pain points of react is the communication between components in non-parent-child relationships, and its official documentation does not shy away from this:

For communication between two components that don't have a parent-child relationship, you can set up your own global event system. Subscribe to events in componentDidMount (), unsubscribe in componentWillUnmount (), and call setState () when you receive an event .

Redux can be regarded as the "global event system". Using redux can make our react applications have a clearer architecture.

In this article, we will explore how to optimize rendering performance for front-end applications based on react and redux architecture. For small react front-end applications, the best optimization is not to optimize because React itself minimizes the real DOM by comparing the differences of the virtual DOM. The structure of the virtual DOM for small React applications is simple, and the time-consuming comparison of the virtual DOM can be ignored. Excluding. For complex front-end projects, the optimization of rendering performance actually refers to how to avoid the comparison of the virtual DOM when it is not necessary to update the DOM.

1. React component life cycle
If a workman wants to be good, he must first sharpen his weapon. Understanding the life cycle of react's components is a prerequisite for optimizing its rendering performance. We can divide the life cycle of react components into 3 large cycles: mount to DOM, update DOM, and uninstall from DOM. React exposes hook functions for each step in the three large loops, allowing us to control the life cycle of components in fine-grained terms.

(1) Mount to the DOM
When a component is inserted into the DOM for the first time, it will go through the basic flow from property and state initialization to DOM rendering, which can be described by:

It must be noted that the process of mounting to the DOM is only once in the entire life cycle of the component, that is, when the component is first inserted into the DOM document stream. There are corresponding restrictions at each step in the process of mounting to the DOM:

You cannot get and set the component's state in getDefaultProps () and getInitialState ().
The state of the component cannot be set in the render () method.
(2) Update the DOM
After the component is mounted on the DOM, once its props and state have been updated, it will enter the DOM update process. Similarly, we can clearly describe the steps of the process through a picture:

componentWillReceiveProps () provides the last time to update the state in this process, and other subsequent functions can no longer update the state of the component. In particular, we should pay attention to the shouldComponentUpdate function. Its result directly affects whether the component needs a virtual DOM comparison. The basic idea of optimizing the rendering performance of the component is to set the shouldComponentUpdate return value to false when it is not necessary, so as to terminate. Update the next steps in the DOM process.

(3) Uninstall from the DOM
The process of unloading from the DOM is relatively simple. React only leaks out componentWillUnmount. This function allows us to intervene in the last moment of DOM unloading.

2. React component rendering performance monitoring
Before performance optimization, let's understand how to monitor the rendering performance of React components. React officially provides Performance Tools, which is also very easy to use. Start a performance analysis through Perf.start, and end a performance analysis through Perf.stop.

import Perf from ‘react-addons-perf’
Perf.start ();
.... your react code
Perf.stop ();
After calling Perf.stop, we can obtain the data indicators of this performance analysis through the API provided by Perf. One of the most useful APIs is Perf.printWasted (). The result shows which components you have made meaningless (does not cause changes to the real DOM) virtual DOM comparison. For example, the following results show that we waste 4ms on the TodoItem component. For a meaningless virtual DOM comparison, we can start here and optimize performance.

The result of Perf.printInclusive () gives the overall time to render each component. From its results, we can find out which component is the performance bottleneck of page rendering.

The API similar to Perf.printInclusive () is Perf.printExclusive (), but the result is the exclusive time of component rendering, that is, it does not include the time spent loading components: processing props, getInitialState, calling componentWillMount and componentDidMount, etc.

3. Basic principles of performance optimization
Using the performance analysis tools from the previous section, we can easily locate which components are the performance bottlenecks of the page and which components make senseless virtual DOM comparisons. In this section, we can explore how to perform front-end applications based on the react and redux architectures. Performance optimization.

3.1 Performance Optimization of General React Components
Through the above React DOM update process, we know that React provides the shouldComponentUpdate function, and its result directly affects whether the component needs to perform a virtual DOM comparison and subsequent real DOM rendering. The default return value of shouldComponentUpdate is true, which implies that React will always perform a virtual DOM comparison, regardless of whether the real DOM needs to be re-rendered. We can override shouldComponentUpdate according to our own business characteristics, and return true only when it is confirmed that the real DOM needs to be changed. The general approach is to compare whether the component's props and state have actually changed. If it changes, it returns true, otherwise it returns false.

 shouldComponentUpdate: function (nextProps, nextState) {
     return! isDeepEqual (this.props, nextProps) ||! isDeepEqual (this.state, nextState);
 }
It is the most common practice to perform a deep comparison (isDeepEqual) to determine whether props and state have changed. Are there any performance issues? If a container-type component has many child nodes, and the child nodes have other child nodes, it is time-consuming to perform a deep comparison (isDeepEqual) on this complex nested object, and it may even offset the risk of avoiding virtual DOM comparison Coming performance gains. React officially recommends using immutable component state in order to implement the shouldComponentUpdate function more efficiently.

What are the advantages of immutable state? Suppose we want to modify the state of a list item in a list, using a non-immutable method:

var item = {
    id: 1,
    text: ‘todo1’,
    status: ‘doing’
}
var oldTodoList = [item1, item2, ...., itemn];
oldTodoList [n-1] .status = ‘done’;
var newTodoList = oldTotoList;
When we need to confirm whether the data of oldTodoList and newTodoList are the same, we can only traverse the list (complexity is O (n)) and compare them in turn:

for (var i = 0; i <oldTodoList.length; i ++) {
    if (isItemEqual (oldTodoList [i], newTodoList [i])) {
        return true;
    }
}
return false;
And if you use immutable:

var newTotoList = oldTodoList.map (function (item) {
    if (item.id == n-1) {
        return Object.assign ((}, item, {status: ‘done‘})
    } else {
        return item;
    }
});
Because every time a change is made, a new object is created, so when comparing whether oldTodoList and newTodoList have changed, you only need to compare their object references (complexity O (1)):

return oldTodoList == newTodoList;
Our optimization direction is to minimize the complexity of all props and state comparison algorithms in shouldComponentUpdate, and shallow comparison (isShallowEqual) is the lowest complexity object comparison algorithm:

 shouldComponentUpdate: function (nextProps, nextState) {
     return! isShallowEqual (this.props, nextProps) ||! isShallowEqual (this.state, nextState);
 }
When the component prop state is immutable, the implementation of shouldComponentUpdate is very simple. We can directly use PureRenderMixin provided by Facebook, which is a shallow comparison of the component's props and state.

var PureRenderMixin = require (‘react-addons-pure-render-mixin’);
React.createClass ({
  mixins: [PureRenderMixin],

  render: function () {
    return <div className = {this.props.className}> foo </ div>;
  }
});
Implementing immutable by yourself is still very challenging. We can use the third-party library ImmutableJS, which is a heavy-duty library suitable for large and complex projects. If your project complexity is not very high, you can use seamless-immutable, which A more lightweight library based on ES5's new feature Object.freeze to avoid object modification, so it can only be compatible with browsers that implement the ES5 standard.

3.2 Understanding the Redux state propagation path
Redux uses an object to store the state of the entire application (global state). When the global state changes, how is the state passed? The answer to this question is crucial to our understanding of rendering performance optimization for redux-based react applications.

Redux divides React components into container-type components and display-type components. Container components are generally generated through the connet function, which subscribes to changes in the global state. With the mapStateToProps function, we can filter the global state and return only the local state that the container component is concerned about:

function mapStateToProps (state) {
    return {todos: state.todos};
}
module.exports = connect (mapStateToProps) (TodoApp);
Every global state change will call the mapStateToProps method of all container components. This method returns a regular Javascript object and merges it into the props of the container component.

The display component does not directly obtain data from the global state, and its data comes from the parent component. When a container-type component changes to the global state, it will propagate the change to all its sub-components (generally display-type components). To put it simply, container-type components and display-type components are parent-child relationships:

Component type Data source Change notification
Presentation component Parent component Parent component notification
Container component global state listening global state
The state transfer path of the component can be described by a tree structure:

3.3 Understanding Redux's Default Performance Optimization
Redux officially has two basic assumptions about container components and global state trees. Violating these assumptions will make Redux's default performance optimizations ineffective:

1. The container component must be Pure Component, that is, the component only depends on state and props
2. Any change in the global state tree is immutable
There is a reason for this specification: As we mentioned above, every time the global state changes, all container components will be notified, and each container component needs to pass 

The shouldComponentUpdate function is used to determine whether the local state of interest has changed and whether it needs to be re-rendered. By default, the shouldComponentUpdate of the React component always returns true. There seems to be a serious performance issue here: Any change in the global state will make the page All components enter the process of updating the DOM

Fortunately, container components generated using the official Redux API function connect will provide a shouldComponentUpdate function by default, which performs a shallow comparison of props and state. If we do not follow Redux's immutable state specification and Pure Component specification, the default shouldComponentUpdate function of a container component is invalid.

In compliance with Redux's immutable state specification, when the default shouldComponentUpdate function of a container component returns true, it indicates that its corresponding local state has changed, and the state needs to be propagated to each sub-component, and all corresponding sub-components will also Perform a virtual DOM comparison to determine if you need to re-render. As shown, after the state of container component # 1 changes, all sub-components will perform a virtual DOM comparison:

Since the display component has no awareness of the global state, we can use React's conventional methods to optimize the rendering performance of the display. Implement the shouldComponentUpdate function for each display component using the general React component performance optimization scheme mentioned in Section 3.1:

 shouldComponentUpdate: function (nextProps, nextState) {
     return! isShallowEqual (this.props, nextProps) ||! isShallowEqual (this.state, nextState);
 }
We can avoid redundant virtual DOM comparisons for display components. For example, when only the display component # 1.1 needs to be re-rendered, other components at the same level will not be compared with the virtual DOM. For example, when only the display component # 1.1 needs to be re-rendered, other components at the same level will not be compared with the virtual DOM.

Conclusion: At the container component level, Redux provides us with the default performance optimization solution; at the display component level, we can use the conventional React component performance optimization solution.

React + redux rendering performance optimization principle

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.