React Update mechanism

React rendering mechanism

The corresponding ReactElement object, also known as the VDOM, is generated according to the JSX code, and the old and new DOM are compared according to the internal DIFF algorithm, and then the real DOM is rendered on the interface

React calls the Render method of React when the props or state changes, creating a different tree

React needs to determine how to effectively update the UI based on the difference between the two different trees:

  • If a tree is completely updated by reference to another tree, then even the most advanced algorithm has a complexity of O(n^3^), where n is the number of elements in the tree;
  • Grfia. Dlsi. Ua. Es/ml/algorith…
  • If this algorithm was used in React, the amount of computation required to display 1000 elements would be on the order of a billion;
  • This overhead was too expensive, and React’s update performance became inefficient;

So React optimized this algorithm to O(n).

  1. Nodes at the same level are compared with each other, not across nodes
  2. Different types of nodes produce different tree structures
  3. In development, keys can be used to specify which nodes remain stable under different renders

1.1 Compare different types of elements

When nodes have different elements, React disassembles the existing tree and creates a new one:

  • When an element from<a>become<img>, from <Article>become<Comment>, or from<Button>become<div>Triggers a complete rebuild process;
  • When a tree is unmounted, the corresponding DOM node is also destroyed and the component instance executescomponentWillUnmount()Methods;
  • When a new tree is created, the corresponding DOM node is created and inserted into the DOM, and the component instance is executedcomponentWillMount()Method, and thencomponentDidMount()Methods;

Because the element is converted from div to span, the entire subtree is destroyed and rebuilt again

So React destroys the Counter component and reloads a new one instead of reusing it;

1.2 Compare elements of the same type

When you compare two React elements of the same type, React preserves the DOM node and only compares and updates the changed attributes

  • Component will remain the same, will React to update the component props, and invoke componentWillReceiveProps () and componentWillUpdate () method;

  • Next, the render() method is called, and the diff algorithm recurses between the previous result and the new one;

By comparing the two elements, React knows that it only needs to change the className attribute on the DOM element

When updating the style property, React updates only the properties that have changed

By comparing the two elements, React knows that it only needs to change the color style on the DOM element, not the fontWeight

1.3 Recurse to child nodes

By default, React iterates through lists of both child elements while recursively iterating through the child elements of a DOM node. When a difference occurs, a mutation is generated.

  1. The first two comparisons are identical, so no mutation will occur
  2. For the final comparison, a mutation is generated and inserted into the new DOM tree

React produces a mutation for each child element instead of holding it

[Fixed] Interstellar and Inception invariants will be re-rendered

This inefficient way of comparing results in performance problems;

1.4 Key Functions

As you can see from the previous example, if an insert is made at the non-last item in the list, everything from the insert to the last item needs to be re-rendered

In this case, you need to add a key to the list. The key can reuse components as much as possible when the list is updated, especially when it is inserted, and improve the performance of the DIFF algorithm

Insert data at the bottom of the list

In this case, it doesn’t really matter

Insert data before the last item in the list

In this way, in the case of no key, all li needs to be modified;

When a child element (in this case li) has a key, React uses the key to match the child element on the original tree with the child element on the latest tree:

  1. If there is a key to compare each item, React will know which elements are newly inserted or just need to change position
  2. If it is newly inserted, the new element is inserted
  3. React will simply shift the element if it only needs to change position

Key notes:

  1. Key should be unique;

  2. Do not use a random number for key (random number will regenerate a number in the next render)

  3. Using index as the key is not optimized for performance

    Because when index is used as the key, the index changes every time the non-last item is inserted

    For example: [Klaus, Alice], where Klaus’ index is 0 and Alice’s index is 1

    Insert a John in the header and the array becomes [‘Jhon’, ‘Klaus’, ‘Alice’],

    Klaus’ index changes from 0 to 1, and Alice’s index changes from 1 to 2

1.5 Render function call

Let’s use an earlier nested example:

  • In the App, we added a counter code;
  • When +1 is clicked, the App’s render function is called again.
  • When the render function of the App is called, the render function of all subcomponents will be called again.

Then, we can consider that in the future development, as long as we modify the data in the App, all components need to be re-render and diff calculation, and the performance is bound to be very low:

  • In fact, many components do not have to be rerendered;

  • They should call render with the premise that they call their render methods when the dependent data (state, props) changes

shouldComponentUpdate

React provides a life cycle method shouldComponentUpdate (in many cases, we call it SCU for short), which takes arguments and returns values:

This method takes two parameters:

  1. Parameter 1: nextProps Specifies the latest props property after modification
  2. Parameter 2: nextState Indicates the latest state attribute after modification
  3. NextContext: The latest context property after modification (not used)
  4. The return value of this method is a Boolean type
    • Return true, then call the render method;
    • Return false, so there is no need to call render;
    • The default returns true, meaning that the render method is called whenever state changes;

Example:

Requirement: The interface now has a count and message variable

The interface relies only on the count variable, but not on the message variable

So when you change, you only change the data in the interface if the count data changesCopy the code

If it was the data in message that sent the change, the data is not rendered

import React, { Component } from 'react'

export default class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      count: 0.message: 'Hello World'}}render() {
    return (
      <div>
        <div> Count: { this.state.count }</div>
        <button onClick={() = > this.increment() }> +1 </button>
        <button onClick={() = > this.changeText() }>ChangeText</button>
      </div>)}shouldComponentUpdate(nextProps, nextState) {
    if (this.state.count ! == nextState.count) {return true
    }

    return false
  }

  increment() {
    this.setState({
      count: this.state.count + 1})}changeText() {
    this.setState({
      message: 'Hello React'}}})Copy the code

At this point, a problem arises

If we had to implement shouldComponentUpdate manually for all our classes, that would be a lot more work for us developers

React provides a PureComponent class to help implement it

Do not use pureComponent

import React, { Component } from 'react'


class Header extends Component {
  render() {
    console.log('Header render() is called ')
    return <h2>Header</h2>}}class Body extends Component {
  render() {
    console.log('Body render() is called ')
    return <h3>Body</h3>}}class Footer extends Component {
  render() {
    console.log('Footer render() is called ')
    return <h4>Footer</h4>}}class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      count: 0}}render() {
    console.log('App render() is called ')
    return (
      <div>
        <div>Count: { this.state.count }</div>
        <button onClick={() = > this.increment() }>increment</button>
        <Header />
        <Body />
        <Footer />
      </div>)}increment() {
    this.setState({
      count: this.state.count + 1}}})export default App;
Copy the code

The results of

As you can see, the render methods of App,Header, Body, and Footer components are called during the initial render

When changing the state method in the App component, the render method of the remaining three components is also called again

The render method in the App is called again

So the Header, Body, and Footer components in the render function are also re-rendered,

Their components are re-rendered

Now change all classes that inherit from Component to inherit from PureComponent

import React, { PureComponent } from 'react'


class Header extends PureComponent {
  render() {
    console.log('Header render() is called ')
    return <h2>Header</h2>}}class Body extends PureComponent {
  render() {
    console.log('Body render() is called ')
    return <h3>Body</h3>}}class Footer extends PureComponent {
  render() {
    console.log('Footer render() is called ')
    return <h4>Footer</h4>}}class App extends PureComponent {
  constructor(props) {
    super(props)

    this.state = {
      count: 0}}render() {
    console.log('App render() is called ')
    return (
      <div>
        <div>Count: { this.state.count }</div>
        <button onClick={() = > this.increment() }>increment</button>
        <Header />
        <Body />
        <Footer />
      </div>)}increment() {
    this.setState({
      count: this.state.count + 1}}})export default App;
Copy the code

The effect

You can see that only the render of App is called,

But the subcomponent’s render is not called,

That’s because the ComponentShouldUpdate method is automatically implemented in PureComponent.

It makes a shallow comparison of the old and new props and state, calling the Render method if they are different, and not if they are the same

So what’s the difference between PureComponent and Component?

In the react ReactBaseClasses. Js

function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
// Identify this component as a pureComponent
pureComponentPrototype.isPureReactComponent = true;
Copy the code

In the React ReactFiberClassComponent

function checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext,) {
  const instance = workInProgress.stateNode;
  
  // If the consumer uses the SCU lifecycle hook, then the user-defined SCU is called
  // SCU is undefined if the user does not define it
  if (typeof instance.shouldComponentUpdate === 'function') {
    
    //...
    
    // Three arguments are actually passed in
    const shouldUpdate = instance.shouldComponentUpdate(
      newProps,
      newState,
      nextContext,
    );

    / /...
      
    // Returns whether the render method needs to be called to rerender
    return shouldUpdate;
  }

  // ctor is short for constructor
  // If the constructor exists and is a PureComponent
  if (ctor.prototype && ctor.prototype.isPureReactComponent) {
    return (
      // Compare props to state to see if it needs to be re-rendered
      // Shallow -- This is a shallow comparison! shallowEqual(oldProps, newProps) || ! shallowEqual(oldState, newState) ); }// The default value, that is, not the default value of the SCU, but the default value of the function calling the SCU will return true
  return true;
}
Copy the code

The React of shallowEqual. Js

function shallowEqual(objA: mixed, objB: mixed) :boolean {
  // If objA and objB are the same object, return true
  if (is(objA, objB)) {
    return true;
  }

  // Props and state should be of type object
  // If one of them is not object
  // There might be no props or state defined to update the component without relying on external state changes
  // Such components do not need to be re-rendered
  if (
    typeofobjA ! = ='object' ||
    objA === null ||
    typeofobjB ! = ='object' ||
    objB === null
  ) {
    return false;
  }

  // Compare all keys in objA and objB
  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  // If the keys are not the same length, then return false
  if(keysA.length ! == keysB.length) {return false;
  }

  // Test for A's keys different from B.
  for (let i = 0; i < keysA.length; i++) {
    if (
      // Does objB have an objA attribute
      // And the corresponding values are consistent! hasOwnProperty.call(objB, keysA[i]) || ! is(objA[keysA[i]], objB[keysA[i]]) ) {return false; }}return true;
}

Copy the code

Therefore, it is recommended to inherit from PureComponent when writing class components in the future

When writing function components, enclose the Memo function

The above code is for class components only. Is it still true for function components?

import React, { PureComponent } from 'react'


function Header() {
  console.log('Header is called. ')
  return <h3>Header</h3>
}

class App extends PureComponent {
  constructor(props) {
    super(props)

    this.state = {
      count: 0}}render() {
    console.log('App render() is called ')
    return (
      <div>
        <div>Count: { this.state.count }</div>
        <button onClick={() = > this.increment() }>increment</button>
        <Header />
      </div>)}increment() {
    this.setState({
      count: this.state.count + 1}}})export default App;
Copy the code

As you can see, pureCompoent only optimizes class components

If you want the same effect on function components, you need to use the Memo function,

The function component is encapsulated using the Memo function, which returns a new component using the shallowEqual method

import React, { PureComponent, memo } from 'react'


const Header = memo(function Header() {
  console.log('Header is called. ')
  return <h3>Header</h3>
})

class App extends PureComponent {
  constructor(props) {
    super(props)

    this.state = {
      count: 0}}render() {
    console.log('App render() is called ')
    return (
      <div>
        <div>Count: { this.state.count }</div>
        <button onClick={() = > this.increment() }>increment</button>
        <Header />
      </div>)}increment() {
    this.setState({
      count: this.state.count + 1}}})export default App;
Copy the code

Thus, the same effect is achieved by using a function wrapped by the Memo function

The previous setState article briefly used the next article on controlled and uncontrolled components and ref