A, pain points

In our mind, React means componentization and high performance. We only need to care about the data as a whole, and how the UI changes between data is completely left to the Diff algorithm of the React Virtual Dom. So that we are very random to manipulate data, basic optimization shouldComponentUpdate is too lazy to write, after all, do not write can also render correctly. However, as the application becomes larger and larger, we will find that the page seems to slow down a bit, especially in the case of many nested components and complex data structure, it takes more than 100ms to change a form item or filter a list. At this time, we need to optimize! Of course, if you don’t run into performance bottlenecks, don’t worry; premature optimization is evil. Here’s a simple solution to maximize React performance. In the next section, we’ll review some background, including JavaScript variable types and the React rendering mechanism, which you can skip if you’re a veteran.

Second, some background knowledge review

1. Variable types

There are two types of JavaScript variable types:

  • Basic types: Six basic data types,UndefinedNullBooleanNumberStringSymbol
  • Reference types: Collectively referred to asObjectType, subdivided into:ObjectThe type,ArrayThe type,DateThe type,RegExpThe type,FunctionType, etc.

Here’s an example:

let p1 = { name: 'neo' };
let p2 = p1;
p2.name = 'dave';
console.log(p1.name); // daveCopy the code

In a reference type, declare an object p1, and assign p1 to p2. In a reference type, assign p1 to the object’s address in the heap, not the heap data. This can save memory, but when the application is complex, you need to be very careful with the data, because a careless change in the value of one variable can affect another variable. If we want them to not affect each other, we need to make a copy of exactly the same data, and the copy is divided into shallow copy and deep copy. The shallow copy only copies the data of the first layer, and the deep copy copies all the levels recursively, which consumes performance.

2. React

In React, each time you setState, the Virtual DOM calculates the difference between the two Virtual DOM objects and then modifies the actual DOM. Because JS is fast in computing and real DOM is relatively slow, Virtual DOM avoids unnecessary real DOM manipulation and React performs well. However, with the increase of application complexity, DOM trees become more and more complex, and a large number of comparison operations also affect performance. For example, if a Table component changes a field in a Tr row and sets state, all other Tr rows will render once, which is not necessary. We can decide whether to update the component with the shouldComponentUpdate function. Most of the time we know which components will not change, so we don’t need to calculate which parts of the virtual DOM.

Third, PureComponent

React15.3 adds a new class called PureComponent, formerly PureRenderMixin, which is basically the same as Component but shallowEqual for components before render. Shallow comparison is similar to shallow replication, comparing only the first layer. Using PureComponent eliminates the need to write shouldComponentUpdate if the props and state of the component are:

  1. The references and the first layer data are unchanged,renderMethod doesn’t fire, that’s what we need to achieve.
  2. Although the first layer of data does not change, the reference changes, resulting in virtualDOMThe waste of calculation.
  3. The first layer data changes, but the reference does not change, will not render, so need to be very careful to manipulate the data.

Four, Immutable. Js

Immutable. Js is a library of persistent data structures introduced by Facebook in 2014. Persistence means that once created, data cannot be changed, and any modification or addition or deletion will return a new Immutable object. It makes it easier to deal with caching, rollback, data change detection and other issues, simplifying development. It also provides a lot of native JS-like methods, Lazy Operation features, and full functional programming.

import { Map } from "immutable";
const map1 = Map({ a: { aa: 1 }, b: 2.c: 3 });
const map2 = map1.set('b'.50); map1 ! == map2;// true
map1.get('b'); / / 2
map2.get('b'); / / 50
map1.get('a') === map2.get('a'); // trueCopy the code

As you can see, changing the properties of map1 returns map2, they don’t point to the same storage space, map1 declares only, and all operations don’t change it.

ImmutableJS provides a large number of methods to update, delete, add data, greatly convenient for us to manipulate data. In addition, there are also native types and ImmutableJS type determination and conversion methods:

import { fromJS, isImmutable } from "immutable";
const obj = fromJS({
  a: 'test'.b: [1.2.4]});// Support mixed types
isImmutable(obj); // true
obj.size(); / / 2
const obj1 = obj.toJS(); // Convert to native 'js' typeCopy the code

The two biggest features of ImmutableJS are: Immutable data structures (persistent data structures) and structural sharing, which ensure that data cannot be modified once created and that old data does not change when new data is created using old data. Unlike native JS, operations on new data affect old data. Structural sharing means that unchanged data shares a single reference, which reduces both the performance cost of deep copy and the memory cost. Here’s an example:

tree

Old values on the left, on the right is the new value, I need to change the value of red node, generate new value changed the red node to the root node path between all nodes, also is the value of all cyan node, the old value without any change, the use of its other places will not be affected, while more than half of the blue nodes or and old value sharing. Inside ImmutableJS, a special data structure is constructed, which combines the original value with a series of private attributes to create the ImmutableJS type. Every time a value is changed, it will first pass the auxiliary detection of the private attribute, then change the corresponding private attribute to be changed and the real value, and finally generate a new value. There will be a lot of optimizations in between, so the performance will be high.

Five, the case

Use React only to see how application performance is wasted. In this example, create-react-app is used, and the test tool is used in Chrome :React Perf. perform

git clone https://github.com/wulv/fe-example.git
cd fe-example/react-table
yarn
yarn startCopy the code

We can open up the page and start recording and then modify a random column of data and end recording and we can see that we only modified one row of data but in Print Wasted we rendered Tr 5 times:

react-table



n-1
render
App
state
DOM
Table
Tr
Component
PureComponent
Tr
shallowEqual

 add = (a)= > {
    const  { data } = this.state;
    data.push(dataGenerate())
    this.setState({
      data
    })
  }Copy the code

Data. push does not change the reference to data, so PureComponent’s shallowEqual returns true without rendering. This is not what we want, so if using Component is necessarily a performance waste, using PureComponent must also ensure that when the Component needs to be updated, props or state returns a new reference, otherwise the UI will not be updated.

At this point, ImmutableJS can show its power because it guarantees that a new Object will be returned each time it is modified. Perform the same operation as in the above example, and you can see:

react-immutablejs



PureComponent
ImmutableJS
PureComponent
PureComponent
PureComponent
ImmutableJS
redux
redux
reducer
clone
assign
ImmutanleJS
lodash
redux + immutable + react-router + express
Github.com/wulv/fe-exa…
pageIndex
store

{
  loading: false.tableData: [{
    "name": "gyu3w0oa5zggkanciclhm2t9"."age": 64."height": 121."width": 71."hobby": {
      "movie": {
        "name": "zrah6zrvm9e512qt4typhkt9"."director": "t1c69z1vd4em1lh747dp9zfr"}}}].totle: 0
}Copy the code

If I need to quickly change width to 90, compare the difference between using deep copy, Object.assign, and ImmutableJS:

// payload = { name: 'gyu3w0oa5zggkanciclhm2t9', width: 90 }
// 1. Use deep copy
 updateWidth(state, payload) {
    const newState = deepClone(state);
    return newState.tableData.map(item= > {
      if (tem.name === payload.name) {
        item.width = payload.width;
      }
      return item;
    });
  }
// 2. Use object.assign
 updateWidth(state, payload) {
    return Object.assign({}, state, {
      tableData: state.state.map(item= > {
        if (item.name === payload.name) {
          return Object.assign({}, item, { width: payload.width });
        }
        returnitem; })})}// use ImmutableJS
 updateWidth(state, payload) {
  return state.update('tableData', list => list.update(
      list.findIndex((item) = > item.get('name') === payload.name),
    item => item.set('width', payload.width)));
  }Copy the code

Using deep copy is an expensive operation, and the references are all changed, which will inevitably result in re-render. Object.assign will shallow copy the first layer, which will not render the first layer, but will also copy all other attributes at once, which is not necessary. Only using ImmutableJS did the change perfectly and with minimal code.

Six, advantages and disadvantages

As you can see, ImmutableJS combined with PureComponent can greatly reduce the number of re-render applications, which can greatly improve performance. But there are still some shortcomings:

  1. Obtaining component properties must be usedgetgetInOperation (exceptRecordType), this and native.The operation is much more cumbersome and requires a lot of modification if the component has already been written.
  2. ImmutableJSThe library is quite large, about 56K, opengzip16K after compression.
  3. Cost of learning.
  4. Difficult to debug, inredux-loggerThe inside needs to bestateTransformerExecute in configurationstate.toJS().

7. Best practices

In fact, the important thing is that the programmer needs to be aware of performance optimization, and be familiar with the nature of JS reference types. It is more important to understand the nature of things than to know how to use a framework or library. The effect of ImmutableJS can be achieved with other methods, such as adding data using the destruct operator:

 add = (a)= > {
    const  { data } = this.state;
    this.setState({
      data: [...data, dataGenerate()]
    })
  }Copy the code

However, if the data is deeply nested, it is more difficult to write. Here are some tips:

  1. There are also two lightweight libraries that can implement immutable data structures: Seamless -immutable or immutability- Helper, though the principle is completely different and less efficient.
  2. Avoid heavy usetoJSOperation, which wastes performance.
  3. Don’t make it simpleJavaScriptObject and theImmutable.JShybrid
  4. In combination withreduxWhen, to useimport { combineReducers } from 'redux-immutablejs';Because thereduxcombineReducersexpectstateIt’s a pure onejsObject.
  5. As far as possible willstateDesigned to be flat.
  6. Do not use presentation componentsImmutableData structure.
  7. Don’t inrenderOne of the functionsPureComponentThe component’spropsusebind(this)orstyle={ { width: '100px' } }Because theshallowEqualIt’s bound to fail.

8. Reference links

  • Immutable.js, persistent data structures and structural sharing
  • immutable.js is much faster than native javascript
  • Immutable; React; Immutable

This article was originally posted on The Uptech blog.