📜 ⬆️ ⬇️

Performance Optimization in React

React. Advanced Guides. Part Five


Continuation of a series of translations of the section "Advanced Guides" (Advanced Guides) of the official documentation of the React.js library.


Performance Optimization in React


Internally, React uses several advanced techniques that minimize the number of expensive DOM operations required to update the user interface. For most applications using React, the speed of the resulting interface is sufficient without additional actions to optimize performance. However, there are several ways you can speed up your React application.



Using the final (production) build


If you are testing performance or experiencing performance problems in your React application, make sure you test the minified final (production) build:



 new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify('production') } }), new webpack.optimize.UglifyJsPlugin() 

A development build includes additional warnings that help develop an application, but they slow down an application by performing additional actions.


Component profiling using Timeline in Chrome


Using performance tools in supported browsers, in development mode, you can visualize how components are mounted, updated and unmounted. For example:


React components in Timeline Chrome


Perform the following steps in Chrome:


  1. Load your application with the ?react_perf parameter in the query string (for example: http://localhost:3000/?react_perf ).


  2. Open the Timeline tab in the Chrome Developer Tools and click Record .


  3. Perform the actions you want to profile. Do not record actions for more than 20 seconds - otherwise Chrome may hang.


  4. Stop recording.


  5. React events will be grouped under the User Timing label.

Please note that these numbers are relative, since components will be displayed faster in production mode . Ultimately, this should help you to imagine where the user interface receives updates by mistake, and how deep and often updates your user interface.


Currently, this feature is supported only by Chrome, Edge and IE, but since we use the User Timing API standard, we expect other browsers to add support for this feature.


Avoid unnecessary redrawing.


React creates and maintains its own internal representation of the displayed user interface. It includes elements of React returned by your components. This allows React to avoid creating existing DOM nodes and accessing them beyond necessity, which may be slower than operations with JavaScript objects. Sometimes this model is called the “virtual DOM”, although Native React (React Native) works as well.


When the properties of the component (props) or state change, React compares the new returned version of the item with the one previously displayed in the DOM, and if they are not equivalent, it updates the DOM.


In some cases, your component can be accelerated by redefining the life cycle function shouldComponentUpdate , which is called before the re-mapping (re-rendering) process begins. The default function definition returns true , allowing React to update:


 shouldComponentUpdate(nextProps, nextState) { return true; } 

If you know that in some cases your component does not need to be updated, you can return false from the shouldComponentUpdate function to skip the re-mapping (re-rendering) process, including calling the render() method in the current component and down the hierarchy.


shouldComponentUpdate in action


The figure shows a tree of components. The SCU label displays the result returned by the function shouldComponentUpdate , and the vDOMEq label vDOMEq whether the React element is equivalent to the previous view. The color of the circles indicates the need to redraw the component.


Component tree


In the C2 component, the function shouldComponentUpdate returned false , as a result of which the entire subtree, starting from C2 and below, React will not redraw (call the render function), and for this you do not even have to call the function shouldComponentUpdate in components C4 and C5.


For C1 and C3 - shouldComponentUpdate returned true , so React had to go down and check the descendants. For C6, shouldComponentUpdate returned true , and since these elements are not equivalent to a virtual DOM, React had to be updated with DOM.


Well, the last interesting option is C8. React had to render the component, but since the new internal representation of the React element is equivalent to the previous one; it was not necessary to update the DOM.


Note that React had to make changes to the DOM only for C6, which were inevitable. In the case of C8 - we were helped by comparing the rendered React elements, for the C2 tree and the C7 component we didn’t even have to compare the elements - shouldComponentUpdate returned false and the render method was not called.


Examples


Imagine the option that our component changes only when the props.color property or state.count . We can implement the verification of these cases in the function shouldComponentUpdate :


 class CounterButton extends React.Component { constructor(props) { super(props); this.state = {count: 1}; } shouldComponentUpdate(nextProps, nextState) { if (this.props.color !== nextProps.color) { return true; } if (this.state.count !== nextState.count) { return true; } return false; } render() { return ( <button color={this.props.color} onClick={() => this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button> ); } } 

In this code, the function shouldComponentUpdate checks for any changes to the props.color property or the state.count state. If their values ​​have not changed, the component is not updated. If your component is more complex, you can use a similar template, but producing a "superficial comparison" of all the properties (fields) of props and state to determine if the component needs to be updated. This template is used quite often, so React has a helper for implementing this logic - just inherit your component from React.PureComponent . The following code achieves the same goal as the previous one, but it is actually simpler:


 class CounterButton extends React.PureComponent { constructor(props) { super(props); this.state = {count: 1}; } render() { return ( <button color={this.props.color} onClick={() => this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button> ); } } 

In most cases, you can use React.PureComponent instead of writing your own function shouldComponentUpdate . However, React.PureComponent implements only a superficial comparison, and there are cases when the properties (props) or states can be changed in such a way that a superficial comparison is not enough - such a situation can occur with more complex data structures. For example, suppose you need to implement a ListOfWords component to display a list of words separated by commas, with the parent component WordAdder , which, when a button is WordAdder , adds a word to the list. The following code will not work correctly:


 class ListOfWords extends React.PureComponent { render() { return <div>{this.props.words.join(',')}</div>; } } class WordAdder extends React.Component { constructor(props) { super(props); this.state = { words: ['marklar'] }; this.handleClick = this.handleClick.bind(this); } handleClick() { //           const words = this.state.words; words.push('marklar'); this.setState({words: words}); } render() { return ( <div> <button onClick={this.handleClick} /> <ListOfWords words={this.state.words} /> </div> ); } } 

The problem is that PureComponent makes a simple superficial comparison between the old and new values ​​of the this.props.words array. A simple comparison returns true if both compared values ​​refer to the same object (array). Since the specified code of the handleClick method of the handleClick component WordAdder changes the array of words , the comparison of the old and the new value of this.props.words returns equivalence, despite the fact that the words themselves in the array have changed. The ListOfWords component ListOfWords not be updated, having new words that need to be displayed.


The power of immutable data


The easiest way to solve this problem is to not change (not mutate) the values ​​that you use in properties (props) or state. For example, the handleClick method can be rewritten using the concat method, which returns a shallow copy of the array with the addition of new values:


 handleClick() { this.setState(prevState => ({ words: prevState.words.concat(['marklar']) })); } 

ES6 supports spreading syntax for arrays, which can make the code even easier. If you use the Create React App to create your application, this syntax is available by default.


 handleClick() { this.setState(prevState => ({ words: [...prevState.words, 'marklar'], })); }; 

In the same way, you can rewrite the code to avoid changes (mutations) of objects. For example, imagine that we have a colormap object and we want to write a function that changes the value of colormap.right and sets it to 'blue' . We could mistakenly write:


 function updateColorMap(colormap) { colormap.right = 'blue'; } 

To implement this without mutations of the original object, we can use the Object.assign method:


 function updateColorMap(colormap) { return Object.assign({}, colormap, {right: 'blue'}); } 

updateColorMap now returns a new object, rather than changing the old one. Object.assign is included in ES6 and requires the inclusion of babel-polyfill.


In ES6, there is the possibility of expanding (spread) the properties of an object and therefore we can implement an update of an object without mutation even more simply:


 function updateColorMap(colormap) { return {...colormap, right: 'blue'}; } 

If you use the Create React App to create your application, Object.assign and the Object.assign syntax for objects are available by default.


Using immutable data structures


Immutable.js is another way to solve this problem. This library provides immutable, permanent collections that work through structural exchange:



Immutability makes change tracking easy. Changes always lead to the creation of a new object and we can only check whether the link to the object has changed. For example, in the native JavaScript code:


 const x = { foo: "bar" }; const y = x; y.foo = "baz"; x === y; // true 

Although y been changed, y refers to the same object as x - their comparison returns true . We can rewrite this code using immutable.js:


 const SomeRecord = Immutable.Record({ foo: null }); const x = new SomeRecord({ foo: 'bar' }); const y = x.set('foo', 'baz'); x === y; // false 

In this case, because when x was changed, a reference to the new object was returned, we can safely assume that x has changed.


Two more libraries that can help use immutable data are seamless-immutable and immutability-helper .


Immutable data structures provide you with the easiest way to track changes in objects — and this is what we need to use shouldComponentUpdate , which in most cases will give you a good performance boost.


Previous parts:



Original source: React - Advanced Guides - Optimizing Performance


')

Source: https://habr.com/ru/post/319536/


All Articles