1. A singleReactComponent performance optimization

React uses the Virtual DOM to improve rendering performance. Although every page update is a re-rendering of components, it does not discard all the previously rendered content and start over. Instead, it uses the Virtual DOM to calculate the minimal changes to the DOM tree. This is why React renders fast by default.

However, while the Virtual DOM can minimize the amount of DOM modification at a time, it can still be a complex process to calculate and compare Virtual DOM. If you can determine that the render result will not change before you start computing the Virtual DOM, you can simply leave the Virtual DOM calculation and comparison alone, which is much faster.

react-reduxtheshouldComponentUpdate

ShouldComponentUpdate, which we introduced earlier, is probably the most important function in the React component lifecycle function other than Render. The Render function determines’ what component renders’ and the shouldComponentUpdate function determines’ when not to rerender ‘.

The React Component’s parent Component provides a shouldComponentUpdate implementation that simply returns true, which means that all lifecycle functions, including the render function, should be called every time the React Component is updated. Calculate the Virtual DOM based on the return result of the Render function.

To recap, using the React-Redux library, we split the React component that performs a function into two parts:

  • Dumb component: Just the view part, dealing with the ‘how does the component look’ thing, this dumb component is usually enough to be represented as a stateless component of a function, not even a class, just a function definition is enough.
  • Container components: Responsible for the logical part, dealing with ‘how components work’. The container component is stateful and maintains andRedux StoreSynchronization of the state on, butreact-reduxtheconnectFunctions encapsulate this synchronized logic, and we don’t even see the class in the code, often directly exportedconnectReturn the result of the function’s execution.

export default connect(mapStateToProps,mapDispatchToProps)(TodoItem)

Although not visible from the code, connect actually generates a nameless React component class that implements shouldComponentUpdate. The implementation logic is to compare the props passed to this dumb component to the props of the previous one. If the props are unchanged, then it is assumed that the render must be the same.

The React component’s shouldComponentUpdate function is a big step forward. But in terms of comparing prop to the prop used in the last render. I’m still doing ‘shallow comparisons’ in the simplest way possible, using the javascript default === for comparison. If prop is of type string or number, shallow comparison will consider them identical as long as the values are the same. However, if a prop is of type object, shallow comparisons will only compare whether the two objects are references to the same object. If they are not, they will be considered different prop even if the contents of the two objects are identical. Similarly, a prop of a function type has the same problem. In order for it to know that two prop types are the same, it must point to the same function. If each prop passed in is a newly created function, this will not work.

More than 2.reactComponent performance optimization

When a React component is loaded, updated, and unloaded, a series of component lifecycle functions are called. However, these lifecycle functions are specific to a specific React component. So, in an application, there are a lot of React components combined from top to bottom. What does the rendering process look like between them?

There is no performance tuning to do during the load and unload phases, so let’s focus on the update phase:

2.1 ReactHarmonic stage of

First of all, what is harmonization? React makes minimal changes to the DOM tree by comparing Virtual DOM differences in the update phase. React This process of finding different things in an update is called reconciliation.

The React diff algorithm is not complicated. When comparing the tree structures of two Virtual DOM, the comparison starts recursively from the root node and goes down. On the tree structure, each node can be regarded as the root node of the molecular tree below the node. So the diff algorithm can be executed from any node in the Virtual DOM.

See the React Diff algorithm for details

React first checks whether the root nodes of two trees are of the same type.

Different node types

If the tree root node type is not the same, it means that the change is too big, also don’t bother to consider whether the original root node of the tree has been moved to other places, direct thought originally that tree structure has been useless, can be thrown away, the need to build a new DOM tree, the React on the original tree component will experience ‘uninstall’ life cycle. Such as:

<div>                   <span>
    <Todos/>= = = ><Todos/>
</div>                  </span>
Copy the code

So, when you compare, you see that the root node is div, and the new node is SPAN, and the type is different. The algorithm then says it must abolish the previous div node and all its children, and then re-render a SPAN node and its children.

Obviously, this is a huge waste of time, but in order to avoid the O(N^3) time complexity of the original Diff algorithm, React had to choose a simpler and faster algorithm.

So, as developers, we have to avoid this kind of wasteful situation.

The node types are the same

If the root nodes of two trees are of the same type, React assumes that the original root node only needs to be updated, and will not be unloaded or cause a re-rendering of the root node.

  • DOM element type:ReactThe DOM element corresponding to the node is preserved, only the attributes and contents of the root node of the tree are compared, and then only the modified parts are updated.
  • ReactComponent type:ReactAt this point, you don’t know how to update the DOM tree because the logic is still inside the component,ReactAll you can do is look at the new nodepropsTo update the component instance of the original root node, causing the component instance to be updated. That is, the following functions are thrown in order:
    • shouldComponentUpdate
    • componentWillReceiveProps
    • componentWillUpdate
    • render
    • componentDidUpdate

In this process, if shouldComponentUpdate returns false, then the update process is stopped, so for maximum performance, every React component must attach shouldComponentUpdate. If it is found that there is no need to re-render, Return false after that.

When a component contains multiple child components,React is straightforward. Taking the TODO application’s to-do list as an example, suppose the initial component looks like this:

<ul>
    <TodoItem text='first' completed={false}/>
    <TodoItem text='second' completed={false}/>
</ul>
Copy the code

After the update, the new component looks like this:

<ul>
    <TodoItem text='first' completed={false}/>
    <TodoItem text='second' completed={false}/>
    <TodoItem text='third' completed={false}/>
</ul>
Copy the code

React will notice that there is an extra Item and create a new Item instance. This Item component instance will need to go through the loading process. For the first two TodoItem instances, React will cause them to update. But as long as the shouldComponentUpdate for both items is done properly, it should return false after checking props, and no real update will occur.

Let’s look at another example. If we want to add an Item instance to the front of the sequence, it looks like this:

<ul>
    <TodoItem text='zero' completed={false}/>
    <TodoItem text='first' completed={false}/>
    <TodoItem text='second' completed={false}/>
</ul>

Copy the code

The TodoItem instance is inserted in the first place. The remaining two components with first and second are updated. ShouldComponentUpdate There’s really no real update going on. But is this really the case?

If React were to behave the way we expect it to, we would have to find the difference between the sequences of the two subcomponents, and the algorithmic complexity to calculate the difference between the sequences would be O(N^2). React gets away from being efficient. Instead of looking for exact differences between the two sequences, React directly compares each subcomponent.

In the example above, React will first consider changing the text of the TodoItem component instance whose text is first to zero and the text of the TodoItem component instance whose text is second to first. The last TodoItem component instance is added, and the text content is changed to Second. As a result, the text property of the two existing TodoItem instances was changed, forcing them to go through an update process that created a new TodoItem instance to display Second.

The ideal situation is to add only one TodoItem component, but it actually causes two TodoItem instances to be updated, which is obviously wasteful.

React, of course, recognizes this problem and provides a way to overcome this waste, which is key.

2.2 keyThe use of the

React does not use an O(N^2) algorithm to compare two subcomponents. By default, React confirms that each subcomponent is uniquely identified in the component sequence by its location. As a result, he had no idea which subcomponents hadn’t actually changed. To make React smarter, we needed to give it some help.

We can use a key to tell React the unique identity of each component. Here’s an example:

<ul>
    <TodoItem key={1} text='first' completed={false}/>
    <TodoItem key={2} text='second' completed={false}/>
</ul>
Copy the code

Add a TodoItem instance in the first place

<ul>
    <TodoItem key={0} text='zero' completed={false}/>
    <TodoItem key={1} text='first' completed={false}/>
    <TodoItem key={2} text='second' completed={false}/>
</ul>
Copy the code

React inserts the new TodoItem instances at first, and uses only the old props to start the update process for the existing TodoItem instances. ShouldComponentUpdate should work to avoid unnecessary updates.

3. UsereselectImproved data acquisition performance

React and Redux are both data-driven rendering processes. How about optimizing the rendering process as well as the data retrieval process?

The following is an example:

const selectVisibleTodos=(todos,filter) = >{
    switch(filter){
        case FilterTypes.All:
            return todos;
        case FilterTypes.COMPLETED:
            return todos.filter(item= >item.completed);
        case FilterTypes.UNCOMPLETED:
            return todos.filter(item= >! item.completed);default:
            throw new Error('unSupported filter'); }}const mapStateToProps=(state) = >{
    return {
        todos:selectVisibleTodos(state.todos,state.filter)
    }
}

Copy the code

As an important part of getting data from the Redux Store, the mapStateToProps function has to be fast. There isn’t much room to optimize the operation itself in the code. It is calculated according to the values of todos and Filter in the Redux Store state tree. However, this calculation requires traversing the array on the TODOS field, which is a bit cumbersome when the array is large and needs to be recalculated every time it is re-rendered.

In fact, not every rerendering of a TodoItem must perform the calculation in selectVisibleTodos. If the ToDOS field representing all the items in the Redux Store state tree does not change, and the Filter field representing the current filter does not change, There is no need to iterate through the toDOS array to compute a new result. If the result of the previous calculation can be cached, then the cached data can be reused.

This is how the ResELECT library works: as long as the relevant state hasn’t changed, the last cached result is used directly.

The Reselect library is used to create ‘selectors’. A selector is a function that takes a state parameter and returns the data that one of our mapStateToProps needs.

Reselect’s view of the work of a selector can be divided into two parts, dividing a calculation step into two steps:

  • Step 1: Extract the result of the first layer from the input parameter state. Compare the result of the first layer with the result of the first layer extracted previously. If the result is identical, there is no need to perform the operation of the second part, and the selector can directly return the operation result of the second part. Note that the comparison here uses the javascript === comparison.

  • Step 2: Calculate the final result that the selector needs to return according to the result of the first layer.

Such as:

src/todo/selectors.js

import {createSelector} from 'reselect';
import {FilterTypes} from './constants.js';

export const selectVisibleTodos=createSelector(
[getFilter,getTodos],
(filter,todos)=>{
    switch(filter){
        case FilterTypes.ALL:
            return todos;
        case FilterTypes.COMPLETED:
            return todos.filter(item=>item.completed);
        case FilterTypes.UNCOMPLETED:
            returntodos.filter(item=>! item.completed); default: throw new Error('unsupported filter')}})Copy the code

src/todo/todoList.js

import {selectorVisibleTodos} from '.. /selector.js';
const mapStateToProps=(state)=>{
    return {
        todos:selectorVisibleTodos(state)
    }
}
Copy the code