How to build a high-performance React application?

React is also a front-end application. If you know some of the common performance optimizations for front-end projects, such as resource loading optimization, redraw reduction and backflow reduction, server rendering, and CDN enablement, these are also effective for React.

For the React project, however, one important feature that distinguishes it from traditional front-end projects is that the logic is organized in the form of React components: Components allow the UI to be broken down into separate reusable code fragments, and each fragment to be conceived independently. Therefore, in addition to the general front-end performance optimization methods mentioned above, React also has some performance optimization ideas with its own characteristics, which are basically based on the central idea of “component performance optimization”. Three of the most critical ideas will be recognized in this lecture:

  • Use shouldComponentUpdate to circumvent redundant update logic

  • PureComponent + Immutable.js

  • The React. Memo and useMemo

[Note] : These three ideas are also at the heart of the “performance optimization” part of the React interview. When answering similar questions, regardless of other details of the optimization strategy can be remembered, the above three points must try to answer all.

1, simple thinking: use shouldComponentUpdate

ShouldComponentUpdate is a life cycle of the React class component. ShouldComponentUpdate (shouldComponentUpdate) shouldComponentUpdate (shouldComponentUpdate) shouldComponentUpdate (shouldComponentUpdate)

ShouldComponentUpdate is called as follows:

shouldComponentUpdate(nextProps, nextState)
Copy the code

The Render method can be quite time-consuming due to the construction and comparison of the virtual DOM. React calls render a lot of the time. To avoid the performance overhead of unnecessary render operations, React provides shouldComponentUpdate. The React component uses the return value of shouldComponentUpdate to determine whether to re-render the component after the life cycle of the method.

ShouldComponentUpdate defaults to true, that is, “unconditional re-render”. In the actual development, often by manually filling the decision logic in shouldComponentUpdate, to achieve “conditional re-render”.

Let’s use a Demo to see how shouldComponentUpdate works. There are three components involved in this Demo: child ChildA, ChildB, and parent App component.

Let’s start with the code for the two child components. Here, to simplify the logic unrelated to data changes as much as possible, ChildA and ChildB are only responsible for reading and rendering data from the parent component, and their encoding is shown below, respectively.

  • ChildA. Js:
import React from "react"; Export default class ChildA extends React.Component {render() {console.log("ChildA render method executed "); Return (<div className="childA"> childA: {this.props. Text} </div>); }}Copy the code
  • ChildB. Js:
import React from "react"; Export default class ChildB extends React.Component {render() {console.log("ChildB render method executed "); Return (<div className="childB"> child component B: {this.props. Text} </div>); }}Copy the code
  • In the common parent app.js, ChildA and ChildB are combined and injected with data respectively:
import React from "react"; import ChildA from './ChildA' import ChildB from './ChildB' class App extends React.Component { state = { textA: } changeA = () => {this.setState({textA: 'A text is modified '})} changeB = () => {this.setState({textB: } render() {return (<div className="App"> <div className="container"> <button OnClick ={this.changeA}> </button onClick={this.changeB}> </button> <ul> <li> <ChildA text={this.state.textA}/> </li> <li> <ChildB text={this.state.textB}/> </li> </ul> </div> </div> ); } } export default App;Copy the code

The final rendering effect of App components on the interface is shown in the figure below, where the two sub-components are circled with different colors respectively:

You can modify the copy in ChildA and ChildB by clicking the left and right buttons, respectively.

Since the render function of both components must be triggered for the first rendering, the console output after the mount is complete looks something like this:

Next click the button on the left and try to modify the text at A. As you can see, only A has changed the rendering effect, as shown in the arrow below:

But if you open the console, you’ll find output like the following:

This output tells us that after the click, not only ChildA’s re-render was triggered, but ChildB’s re-render was triggered as well.

In React, all child components are updated unconditionally whenever the parent component is updated. This caused the ChildB props to go through the update process even though nothing had changed and there were no points of its own that needed to be updated.

[Note] : The same situation applies to the update of the component itself: when the component itself calls setState, it will go through the update process regardless of whether the state content before and after setState has really changed.

In this update flow, shouldComponentUpdate is not defined manually, so it will return the default value of “true”. “True” means that there is no stop to the update process, known as “unconditional re-render”. In this case, shouldComponentUpdate should be used to control the update process and avoid meaningless re-render.

We can now add a shouldComponentUpdate logic to ChildB:

ShouldComponentUpdate (nextProps, nextState) {// Check if the text property is changed before and after the parent component is updated. If (nextProps. Text === this.props. Text) {return false} if(nextProps. Text === this.Copy the code

In this logic, the mutable data in ChildB, which is the this.props. Text property, is checked.

So shouldComponentUpdate acts as a ‘keeper’ when the parent App component is updated and tries to trigger the ChildB update process: It checks if the new props. Text is the same as the previous value. If so, there is no need to update the ChildB. It only “permits” re-render to occur if the props. Text does change.

With shouldComponentUpdate, when you click the left button again and try to modify ChildA’s rendering, the console output should look something like this:

As you can see, ChildA’s re-render prompt is now the only thing in the console. ChildB was “as steady as a rock” and managed to dodge an unwanted rendering.

Use shouldComponentUpdate to mediate unnecessary updates and avoid meaningless re-render. This is the most basic and important performance optimization tool for React components. A lot of seemingly advanced gameplay is derived from shouldComponentUpdate. PureComponent is a good example of this type of play.

2. Advanced gameplay: PureComponent + Immutable. Js

1) PureComponent: Helps you schedule the update decision logic in advance

ShouldComponentUpdate while shouldComponentUpdate addresses some of the performance issues, shouldComponentUpdate should be implemented manually every time re-render is avoided. Repeatedly write relevant shouldComponentUpdate logic really let a person feel tired, even doubt the life, and had to heartfelt opinions – “if shouldComponentUpdate logic can be built into the components in there!” .

React 15.3 clearly hears the developers’ voice by adding a new class called PureComponent that addresses the problem of shouldComponentUpdate logic being tired and tired manually over and over again.

PureComponent differs from Component in that it has a built-in implementation of shouldComponentUpdate: In shouldComponentUpdate, the PureComponent will perform a shallow-comparison of props and state before and after the component is updated and, based on the shallow-comparison, decide whether to continue updating the process.

Shallow comparisons compare values for value types and references for array, object, and other reference types.

In the opening Demo, if you replace the parent of ChildB from Component to PureComponent (the modified code is shown below), then you don’t need to write shouldComponentUpdate manually, Re-render can also be avoided.

import React from "react"; Export default class ChildB extends react.purecomponent {render() {console.log("ChildB render method executed "); Return (<div className="childB"> child component B: {this.props. Text} </div>); }}Copy the code

If you modify the text in ChildA, you will find that ChildB is also unaffected. After clicking the button on the left, the output content of the console is highlighted as follows:

In the case of value type data, PureComponent is invincible. But if the data type is a reference type, this logic of shallow comparisons carries two risks:

  • If the data content remains the same but the reference is changed, shallow comparisons will still assume that “the data has changed”, triggering an unnecessary update and resulting in overrendering.

  • If the data content has changed but the reference has not, a shallow comparison will say “the data has not changed” and block an update, resulting in no rendering.

What to do? Immutable. Js to help!

2) Immutable values allow change to be hidden

The problem with shallow PureComponent comparisons is essentially the result of an imprecise judgment of “change”. Is there a way to make a connection between the change in reference and the change in content?

This is what Immutable does.

Immutable.js is a practice of the idea that Immutable values are not mutable. It was launched in 2014 by the Facebook team, which positioned it as a “library to implement persistent data structures.” Persistent data means that once it is created, it cannot be changed. Any modification of the current data results in the return of a new object. This strictly links changes in data content with references to data, making “change” invisible.

Here is a simple example to illustrate the effect of Immutable. Take a look at the following code:

Import {Map} from 'immutable' import {Map} from 'immutable' import {Map} from 'immutable' import {Map} from 'immutable' import {Map} from 'immutable' Const changedMap = basemap.set ({age: 1) const changedMap = basemap.set ({age: 1) const changedMap = basemap.set ({age: 1) const changedMap = basemap.set ({age: 1) 100}) // We will find that modifying baseMap will return a new object, The reference to this object is different from baseMap console.log('baseMap === changedMap', baseMap === changedMap)Copy the code

PureComonent and Immutable. Js are gay friends! In real development, using PureComonent and Immutable. Js as the right-hand side of development, you will find that the quality of your development will be greatly improved!

It is important to note that Immutable. Js may not be accepted as an optimal solution in all scenarios due to its learning costs. Therefore, it is a good idea for some teams to create public classes based on PureComonent and immutable.js that combine the two and improve the development experience by rewriting setState.

3. Performance optimization of function components: React.memo and useMemo

These are all optimization ideas for class components. Is there a general way to prevent over-re-render in function components? Then meet together * * version of “function” shouldComponentUpdate/Purecomponent — the React. Memo * *.

1) the React. Memo: “function” shouldComponentUpdate/PureComponent

React. Memo is a top-level function exported by React. It is essentially a higher-order component that wraps function components. The basic invocation is shown in the following code:

import React from "react"; Function FunctionDemo(props) {return XXX} // areEqual is the second entry to the memo, Function areEqual(prevProps, nextProps) { /* return true if passing nextProps to render would return the same result as passing prevProps to render, React.memo export default react. memo(FunctionDemo, areEqual);Copy the code

The react. memo will “remember” the render result of the function component, and it will directly reuse the last render result if the component compares the props to the previous one. If the component will render the same results under the same props, then it is a good choice to wrap it with react.Memo.

As you can see from the example, the react. memo receives two parameters. The first parameter is the target component to render, and the second parameter, areEqual, is used to carry over the comparison logic for props. The same things we did in shouldComponentUpdate can now be done in areEqual.

For example, the ChildB Component in the first Demo can be modified with Function Component + React.memo. The ChildB code is as follows:

import React from "react"; Function ChildB(props) {console.log("ChildB render logic executed "); function ChildB(props) {console.log("ChildB render logic executed "); Return (<div className="childB"> < props. Text} </div>); } function areEqual(prevProps, prevProps, NextProps) {if(prevProps. Text === nextProps. Text) {return true} return false} // Wrap ChildB export with react. memo default React.memo(ChildB, areEqual);Copy the code

This component is equivalent to the ChildB class with shouldComponentUpdate.

The areEqual function is an optional argument. The react. memo function works when areEqual is not passed, just like the PureComponent — the React.memo will automatically perform the props shallow comparison logic for the component.

Unlike shouldComponentUpdate, The React.memo is only responsible for comparing props, and is not aware of changes in the component’s internal state.

2) useMemo: A more “detailed” memo

As we can see from the above analysis, The Memo can implement shouldComponentUpdate or PureComponent to control the component-level re-render. But sometimes you want to reuse not the entire component, but one or several parts of the component. UseMemo is needed for this more “refined” control.

In short, react. Memo controls whether a component needs to be rerendered, whereas useMemo controls whether a piece of logic needs to be executed repeatedly.

UseMemo can be used as follows:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Copy the code

You can pass in the target logic as the first argument and the array of the logic’s dependencies as the second argument. This way, useMemo reexecutes the target logic in the first entry only when one of the dependencies in the dependency array changes.

Again using the example from the beginning, now try passing in two attributes to ChildB: text and count, which are a piece of text and a number, respectively. Only the count number changes when the button on the right is clicked. The code of the App component after transformation is as follows:

Class App extends React.Component {state = {textA: 'I am A text ', stateB: {text:' I am B text ', count: 10}} changeA = () => {this.setState({textA: 'A text is modified '})} changeB = () => {this.setState({stateB: {... this.state.stateB, count: 100 } }) } render() { return ( <div className="App"> <div className="container"> <button OnClick ={this.changeA}> </button onClick={this.changeB}> </button> <ul> <li> <ChildA text={this.state.textA}/> </li> <li> <ChildB {... this.state.stateB}/> </li> </ul> </div> </div> ); } } export default App;Copy the code

In ChildB, useMemo is used to support the respective rendering logic for text and count. The modified ChildB code looks like this:

import React,{ useMemo } from "react"; Export default function ChildB({text, count}) {console.log("ChildB render logic executed "); Const renderText = (text)=> {console.log('renderText executed ') return <p> Const renderCount = (count) => {console.log('renderCount executed ') return <p> Const textContent = useMemo(()=>renderText(text),[text]) const countContent = useMemo(()=>renderCount(count),[count]) return ( <div className="childB"> {textContent} {countContent} </div> ); }Copy the code

RenderText and renderCount are executed for the first time. The console output is as follows:

Click the button on the right to modify count, and the modified interface will change as follows:

As you can see, useMemo recalculates the logic for renderCount because count has changed. Text doesn’t change, so renderText logic doesn’t execute at all.

The use of useMemo allows for more fine-grained control of the execution logic of function components (especially in order to avoid costly calculations), as well as compensating for the react.Memo’s inability to perceive the internal state of functions, which is beneficial for overall performance.

4, summarize

Learn the three most important ideas for optimizing React component performance.

These three ideas can not only be used as a store of knowledge in daily practice, but also help us to achieve something in the interview situation. In fact, many people’s answers to the question “React performance optimization” are just scratching the surface, focusing on some unimportant details. If you take the material in this lecture and dig into one or more of these areas, you’ll probably be ahead of most of your peers.

In the next lecture, we will learn the React component’s design patterns for building “high quality applications”.

Learning the source (the article reprinted from) : kaiwu.lagou.com/course/cour…