Analysis of react performance optimization

Time:2020-1-17

As one of the most popular web front-end frameworks, react is flexibleComponentEfficientVirtual Dom, and controllable one-way data flowStateManagement and other features have been the first choice for many developers to build projects. Because of this, many novices think:

React seems to mean componentization and high performance. We only need to care about the data as a whole. How to change the UI between the two data is completely handed over toVirtual DomOfDiffAlgorithm to do. So that we can manipulate the data at will, basically optimize itshouldComponentUpdateI’m also lazy to write. After all, I can render correctly if I don’t write. However, as the application volume grows larger and larger, it seems that the page slows down a bit, especially when there are more components nested and the data structure is more complex. It takes more than 100ms to change a form item or filter a list. At this time, we need to optimize!

So, let’s discuss how react can optimize performance at the code level.

1. Working principle of react

First of all, let’s have a brief understanding of how react works and why it works. Only by understanding its working principle can we understand where to start to optimize react. Because react is completing oncerenderIt’s more about operating virtual DOM, so we mainly focus on react’s understanding of virtual DOM scheduling.

  • Virtual DOM model
  • Life cycle management
  • Setstate mechanism
  • Diff algorithm

Virtual DOM model

Virtual DOM is actually an abstraction of real DOM, a JS object. React all surface operations are actually virtual DOM operations.

Virtual DOM is one of the highlights of reactbatching(batch) and efficientDiffAlgorithm. This allows us to “Refresh” the whole page at any time without worrying about performance problems, and the virtual DOM ensures that only the real changed parts of the interface are actually DOM operated. In the actual development, it is not necessary to care about how virtual DOM works, but understanding its operation mechanism will not only help to better understand the life cycle of react components, but also help to further optimize the react program.

Analysis of react performance optimization


Life cycle management

The react life cycle can be divided into three phases:

  1. Mounting (Mount phase)
  2. Updating (update phase)
  3. Unmounting (unmounting phase)

React16 discards three life cycle functions:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

Note: currently in version 16componentWillMountcomponentWillReceivePropscomponentWillUpdateThese three life cycle functions are not deleted completely, and newUNSAFE_componentWillMountUNSAFE_componentWillReceivePropsUNSAFE_componentWillUpdateThree functions. The official plan is to delete all three functions in version 17, and only keep themUNSAVE_The three functions of the prefix are intended for downward compatibility, but developers should try to avoid using them as much as possible, instead replacing them with new lifecycle functions.

Instead, there are two new lifecycle functions:

  • static getDerivedStateFromProps
  • getSnapshotBeforeUpdate

The following is the react life cycle flowchart:

Analysis of react performance optimization

Mounting (Mount phase)

The mount phase can also be understood as the initialization phase of a component, that is, inserting our component into the DOM only happens once.
The lifecycle function calls in this phase are as follows:

  • constructor
  • getDerivedStateFromProps
  • componentWillMount/UNSAVE_componentWillMount
  • render
  • componentDidMount

Updating (update phase)

Update phase, when thepropsChanged, or called inside the componentsetStateorforceUpdate, occurs multiple times.
The lifecycle function calls in this phase are as follows:

  • componentWillReceiveProps/UNSAFE_componentWillReceiveProps
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • componentWillUpdate/UNSAFE_componentWillUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate

Unmounting (unmounting phase)

During the uninstall phase, when a component is uninstalled or destroyed, it will only happen once.
There is only one lifecycle function at this stage:

  • componentWillUnmount

Setstate mechanism

setState(updater[, callback])

We know that react’s state management usesimmutableConceptual design, so when you want to change the state value, you can’t use the traditional assignment methodthis.state.counter = 1To modify, you need to callsetState(updater[, callback])Method of modification.

Setstate () queues changes to the component state and tells react that it needs to re render the component and its subcomponents using the updated state. takesetState()Regard asrequestInstead of the command to update the component immediately. For better perceptual performance, react will delay calling it and then update multiple components through one pass. React does not guarantee that changes to state will take effect immediately.

setState()Components are not always updated immediately. It will defer updates in bulk. This causes thesetState()Read immediately afterthis.stateIt has become a hidden danger. To eliminate hidden dangers, please usecomponentDidUpdateperhapssetStateCallback function for(setState(updater, callback)), both of which can be triggered after the update is applied. To set the current state based on the previous state, you can use the updater function with formal parameters.

(state, props) => stateChange

State is a reference to the state of the component when the application changes. Of course, it should not be modified directly. You should use new objects built on state and props to represent changes. For example, suppose we want toprops.stepTo add a state:

this.setState((state, props) => {
  return {counter: state.counter + props.step};
});

updaterReceived in functionstateandpropsAll guaranteed to be up to date.updaterThe return value ofstateMake a shallow merge.

setState()The second parameter of is the optional callback function, which willsetStateExecute when the merge is complete and the component is re rendered. In general, we recommend usingcomponentDidUpdate()Instead.

UnlessshouldComponentUpdate()ReturnfalseOtherwisesetState()The re render operation is always performed. If a mutable object is used and cannot beshouldComponentUpdate()Conditional rendering is implemented in, only called when the old and new states are inconsistentsetState()Unnecessary re rendering can be avoided.


Diff algorithm

DiffThe algorithm is used to calculate the difference between two virtual DOMS, which is the most expensive part of react.

traditionDiffThe algorithm finds the minimum difference between two random trees by comparing the differences recursively and circularly. The algorithm complexity is O (n ^ 3). As you think, such a high complexity algorithm can not meet our needs. React uses a more simple and intuitive algorithm to optimize the complexity of the algorithm to o (n).

React diff algorithm strategy:

  • The cross level movement of DOM nodes in Web UI is very few and can be ignored.
  • Two components with the same class will generate similar tree structure, and two components with different classes will generate different tree structure.
  • For a group of children at the same level, they can be distinguished by unique IDs.

For the convenience of understanding, it can be summarized as follows:

  • a. tree diff
  • b. component diff
  • c. element diff

tree diff

Based on the tree diff strategy, react performsHierarchical comparison and controlOnly compare the nodes in the same color box (all the child nodes of the same parent node). When it is found that a child node does not delete the node and all its child nodes directly, it will not be used for further comparison. In the algorithm level, it only needs to traverse once, and it can complete the comparison of the whole DOM tree without unnecessary comparison.

As shown in the picture:

Analysis of react performance optimization

In the same category of hierarchical comparison and hierarchical control, there will be cross level mobile operations of DOM nodes (in react, DOM nodes are unstable and damage performance, so this situation is not recommended in development). How can react diff solve this problem? As shown in the following figure:

Analysis of react performance optimization

The above describes different DOM node categories at the same level,React diffIn the way of “violence”, instead of splicing a B C directly to D node, a B C created under d after deleting three nodes of a B C is created.

component diff

React is based on components to build applications, and the strategy for comparing components is simple and efficient.

  • For components of the same type, according toVirtual DOMThere are two kinds of changes, which can be usedshouldComponentUpdate()judgeVirtual DOMWhether there is any change? If there is no change, you don’t need to diff, which can save a lot of time. If there is a change, update the relevant nodes
  • For components of different types, the component is judged asdirty componentTo replace all child nodes under the entire component.

As shown in the following figure, when component D is changed to component g, even if the structures of the two components are similar, once react determines that D and G are different types of components, it will not compare the structures of the two components, but directly delete component D and recreate component g and its child nodes. Although when two components are of different types but similar structures, react diff will affect performance, as the official document of react says: there is little chance for different types of components to have similar DOM tree, so this extreme factor is difficult to have a significant impact in the implementation and development process.

Analysis of react performance optimization

element diff

All sub nodes of the same level can be distinguished by key, and follow policies a and B.

Analysis of react performance optimization

For the algorithm without optimization, the way to realize the new old alternation is to delete all the a B C D and then create a new B a D C, which is obviously inefficient. How to optimize the react diff? It is identified by adding key value for each node.

As shown in the figure above, the nodes in the new and old sets are compared by diff. through key, it is found that the nodes in the new and old sets are the same, so there is no need to delete and create the nodes, only to move the positions of the nodes in the old set and update the positions of the nodes in the new set. At this time, react gives the diff results as follows: B and d do not do anything For any operation, a and C can move.

The above analysis shows that there are the same nodes but different locations in the new and old sets. What if there are new nodes and old nodes that need to be deleted? The following picture:

Analysis of react performance optimization

The benefits of adding key:

If the key is not added, the console will issue a warn when the map is traversed. Since it is a warn, it means that the map can be traversed without adding, but after deletion, creation and insertion, the performance can be imagined to be damaged. Adding the key can helpReact diffAlgorithm combinationVirtual DOMFind the most appropriate way to diff, and maximize the realization of efficient diff, namelyChange where you need to!

2. React performance optimization

Because the main cost of performance in react isupdateStagediffAlgorithm, so performance optimization is mainly aimed atdiffThe main optimization methods of the algorithm are summarized as follows:

  • shouldComponentUpdateoptimization
  • ImmutableData structure optimization
  • propsandbindOptimization
  • React keyOptimization
  • Optimization of component development mode

shouldComponentUpdateoptimization

When thepropsorstateAny change will triggerrenderThe same parent component executes therenderFunction, even if thepropsIt will also trigger sub componentsrenderThe execution of a function, in turn, causesReact DiffCompare the virtual DOM nodes before and after the change to determine whether the real DOM needs to be updated.

althoughVirtual DomIt helps us avoid unnecessary real DOM operations, but with the improvement of application complexity, the DOM tree becomes more and more complex, and a large number of comparison operations will also affect performance. For example, one.ListOne of the componentsItemComponent needs to executesetStateMethod, then othersItemComponents are also executed oncerenderThe update operation, in fact, is unnecessary, causing performance loss. At this time, we can customize theshouldComponentUpdateTo avoid unnecessary updates.

The following is a comparison between not customized and customizedshouldComponentUpdateLife cycle pairrenderUpdate impact example:

let renderCount = 0;
class Message extends React.Component {
  render() {
    const { text, updateParentStateless, updateParentState } = this.props;
    renderCount += 1;
    return (
      <div>
        <h2>Shouldcomponentupdate not customized</h2>
        <p>Content from parent component: < strong > {text} < / strong ></p>
        <p>
          < button classname = "BTN BTN default" onclick = {updateparentstateless} > Update parent component stateless change < / button >
          < button classname = "BTN BTN primary" onclick = {updatepartstate} > Update parent component change state < / button >
        </p>
        <p>Sub component render times: < strong > {rendercount} < / strong ></p>
      </div>
    );
  }
}

let parentRenderCount = 0;
class App extends React.Component {
  state = {
    Text: 'initialize content',
    updateCount: 0
  };
  updateParentStateless = () => {
    this.setState({});
  };
  updateParentState = () => {
    this.setState(state => {
      state.updateCount < 3 && state.updateCount++;
      Return {text: ` number of content updates ${state. Updatecount} `}};
    });
  };
  render() {
    parentRenderCount += 1;
    return (
      <div>
        <Message text={this.state.text} updateParentStateless={this.updateParentStateless} updateParentState={this.updateParentState} />
        <p>Times of parent component render: < strong > {parentrendercount} < / strong ></p>
      </div>
    );
  }
}

Analysis of react performance optimization

In the above example, the parent component updates theprops“Or not, for subcomponents”renderThe times are consistent with the parent component, indicating the parent componentrenderTherender

Next, we modify the subcomponents and customize themshouldComponentUpdateLife cycle function:

class Message extends React.Component {
  shouldComponentUpdate(nextProps) {
    if (nextProps.text !== this.props.text) {
       return true;
    }
    return false;
  }
  render() {
    const { text, updateParentStateless, updateParentState } = this.props;
    renderCount += 1;
    return (
      <div>
        <h2>Customize shouldcomponentupdate</h2>
        <p>Content from parent component: < strong > {text} < / strong ></p>
        <p>
          < button classname = "BTN BTN default" onclick = {updateparentstateless} > Update parent component stateless change < / button >
          < button classname = "BTN BTN primary" onclick = {updatepartstate} > Update parent component change state < / button >
        </p>
        <p>Sub component render times: < strong > {rendercount} < / strong ></p>
      </div>
    );
  }
}

Analysis of react performance optimization

Through customizationshouldComponentUpdateLife cycle function to determine whether the text property has been modified, and then decide whether to update the component, avoiding unnecessaryrenderRendering to reduceReact DiffThe cost of virtual DOM comparison.

Similarly, we can usePureComponentComponents optimize it,PureComponentComponents andComponentThe components are similar,PureComponentComponent to shallow comparison(shallowEqual)Props and state are implementedshouldComponentUpdateFunction. We modify the subcomponent again:

class Message extends React.PureComponent {
  render() {
    const { text, updateParentStateless, updateParentState } = this.props;
    renderCount += 1;
    return (
      <div>
        <h2>PureComponent</h2>
        <p>Content from parent component: < strong > {text} < / strong ></p>
        <p>
          < button classname = "BTN BTN default" onclick = {updateparentstateless} > Update parent component stateless change < / button >
          < button classname = "BTN BTN primary" onclick = {updatepartstate} > Update parent component change state < / button >
        </p>
        <p>Sub component render times: < strong > {rendercount} < / strong ></p>
      </div>
    );
  }
}

PureComponentThe shallow comparison of components, similar to the shallow replication of objects, only compares the first level. For deep nesting, it is impossible to accurately compare. Therefore, for complex data typesPureComponentThe shallow comparison of components is not as expected, so you can useImmutableHandle complex data types.


ImmutableData structure optimization

ImmutableThe persistent data structure library launched by Facebook in 2014 aims to solve the immutability problem inherent in JavaScript and provide all the benefits of immutability for applications.

Once the immutable object is created, it can no longer be changed. Any modification or addition or deletion will return a new immutable object. The principle of immutable implementation isPersistent data structure, that is to say, when using old data to create new data, the old data should be available and unchanged at the same time. At the same time, in order to avoid the performance loss caused by deepcopy copying all nodes once, immunotable uses structural sharing, that is, if a node in the object tree changes, only the node and the affected parent node are modified, and other nodes are shared. See the following Animation:

Analysis of react performance optimization

import { Map, List } from 'immutable';

const map1 = Map({
  a: 1,
  b: List([1, 2, 'aa'])
});

const map2 = map1.set('a', 2);

map1 === map2; // false
map1.get('a'); // 1
map2.get('a'); // 2
map.get('b') === map2.get('b'); // true

After the node a of the code MAP1 is modified, MAP2 is not affected, and MAP1 and MAP2 share the unchanged B node.

We knowPureComponentFor the shallow comparison of components, it is impossible to compare the deep nested data structuresImmutableBecause of the persistent data structure and structure sharing characteristic of data structure, the differences of each modification and shallow comparison can be compared accurately.

let renderCount = 0;
class Message extends React.PureComponent {
  render() {
    const { info, updateParentStateless, updateParentState } = this.props;
    renderCount += 1;
    return (
      <div>
        <h2>Using immutable data structure</h2>
        <p>Content from parent component: < strong > {info. Get ('text ')} < / strong ></p>
        <p>
          < button classname = "BTN BTN default" onclick = {updateparentstateless} > Update parent component stateless change < / button >
          < button classname = "BTN BTN primary" onclick = {updatepartstate} > Update parent component change state < / button >
        </p>
        <p>Sub component render times: < strong > {rendercount} < / strong ></p>
      </div>
    );
  }
}

let parentRenderCount = 0;
class App extends React.Component {
  updateCount = 0;
  state = {
    info: Immutable.Map({
      Text: 'initialize content'
    })
  };
  updateParentStateless = () => {
    this.setState({});
  };
  updateParentState = () => {
    this.setState(state => {
      this.updateCount < 3 && this.updateCount++;
      Return {Info: state. Info. Set ('text ', ` number of content updates ${this. Updatecount}')};
    });
  };
  render() {
    parentRenderCount += 1;
    return (
      <div>
        <Message info={this.state.info} updateParentStateless={this.updateParentStateless} updateParentState={this.updateParentState} />
        <p>Times of parent component render: < strong > {parentrendercount} < / strong ></p>
      </div>
    );
  }
}

Analysis of react performance optimization

UsedImmutableData type. Each time the data is modified, a new reference will be returned. When there is no node change, the current reference will be returned. Therefore, click the “update parent component change status” button,PureComponentThe components are compared accuratelypropsChange.


propsandbindOptimization

Most of the data and methods obtained from the parent component by the react subcomponent are throughpropsDelivery, thereforepropsIt is necessary for components, and it is not used correctlypropsWill cause unnecessary performance consumption.bindFunction is usually used to enable the method to get thethisPoints to the current component.

propsoptimization

For componentpropsTry to pass only what you need and avoid using{...props}Because there are too manyprops, or if the level is too deep, it will increaseshouldComponentUpdateThe burden of data comparison in the function. Therefore,propsandstateThe data is as simple and flat as possible. It is convenient to reduce the performance consumption caused by data comparison and array traversal.

IfpropsIf it is an object, it should be passed by defining variables to avoidstyle={{color: '#000'}}Because of the direct assignment, every timerenderWill create a new object (i.e. a new reference), resulting inPureComponentShallow comparison of components failed.

bindoptimization

bindingthisThere are three ways to point it to the current component:

1. Constructor binding

constructor(props) {
  super(props);
  this.handleClick = this.handleClick.bind(this);
}
handleClick(){};

2. Bind when using

handleClick(){};
<a onClick={this.handleClick.bind(this)}>Confirm</a>
<a onClick={() => this.handleClick()}>Cancel</a>

3. Bind when defining

handleClick = () => {};
<a onClick={this.handleClick}>Confirm</a>

Summary of three ways:
1. Using the constructor bind method, each instance can effectively share the function body, so as to use memory more effectively. But when there are many functions to bind, this method is relatively boring. Therefore, it is faster to use arrow function on the premise of knowing that there are not many instances and the function body is not large.
2. Because the binding is inrenderAndrenderIt will be executed many times, every timebindAnd the arrow function produce a new function,PureComponentShallow comparison of components will fail, resulting in additional overhead.
3. Synthesize three kinds of writing methods, the third is the best writing method at present.


React keyOptimization

In the above, we analyzed the use of the same level of child nodeskeyWhen, rightelement diffIt helps a lot. When modifying a node, addkeyValue helpsReact diffAlgorithm combinationVirtual DOMFind the most appropriate way to diff, and maximize the realization of efficient diff. Of course we usekeyThe stable and unique value should be usedkey, not recommendedindexBecause index is not stable for traversal results.

The following example:

const items = sortBy(this.state.sortingTime, this.props.items);
return items.map(item => <img src={item.src} />);

If the order changes, react will diff the elements and determine that the most efficient operation is to change the SRC attribute of several img elements. However, there is still diff computing time, and the efficiency is very low.

And when I add a uniquekeyvalue

return items.map(item => <img src={item.src} key={item.id} />);

When the unique key value react is added, the result is not diff, but direct useinsertBeforeOperation, which is the most efficient way to move DOM nodes.

For example, a teacher reviews many students’ homework. After reading all the homework, the teacher finds that a student’s homework is wrong and asks the student to change it. After the student’s homework is corrected, it is put back into the teacher’s corrected homework and tells the teacher to ask the teacher for help to re grade it. If the students’ names (i.e. the only key) are not written in these assignments, at this time, the teacher cannot distinguish which one is the corrected assignment, and can only painstakingly review it again. If these students have written their own names (i.e. they all have a unique key), the teacher only needs to find out that the name is the student’s homework review.

Note: eachkeyValues are unique and cannot pass within a componentthis.props.keyObtain.


Optimization of component development mode

The concept of react for components is“Combination over inheritance”Therefore, to refine components and decouple them as much as possible can reduce the influence of parent layer components on child layer components and increase the reusability of components.

For example, a list component contains multiple inputs, and input needs to be bound to the current component’sstateTo control the input, if the input component is not split, this will cause the state of one of the input changes to affect the other inputDOM diffOperation.

If the input is split into independentPureComponentThe current component input changesstateIt does not affect the rendering of the parent layer list component, which saves a lot of moneyDOM diffExpenditure.

React components should use pure function components as much as possible, because pure function components are stateless components, which can greatly improve the rendering efficiency of components without the steps such as the execution of react standard component life cycle functions.

summary

React’s high efficiency and flexibility make our development more simple and efficient. Even if we don’t consider optimizing and manipulating data and components at will, we can get a good user experience. However, with the increase of application functions, the complexity of business logic, the page gets stuck and the operation is not smooth. At this time, we need to optimize from all aspects. Therefore, we should always pay attention to the potential performance problems of the code in the process of development. I believe that when you start to develop or optimize your application from the optimization aspects listed above, it will make your react application performance higher and user experience better!