preface

When I changed to a new company, the technology stack I used in my work was also changed from Vue to React. As a React newcomer, frequent summary and thinking could help me understand the framework more quickly and better. Here are some of the performance optimizations I’ve learned using React over the past two months.

Because the current company project fully embraces hooks, it only addresses the function component writing, not the class component writing. Note: this article only deals with code optimization at the business development level. Many common optimization ideas, such as virtual lists, lazy loading of images, throttling and anti-shaking, webpack optimization, etc., will not be covered.

React update mechanism

To optimize the code, let’s take a quick look at the React update mechanism. See below

Let’s focus on steps 1 through 2. When a component’s props or state changes, the current component will render again. All children, grandchildren, great-grandchildren under the current component… Components are rerender regardless of whether they use the props of the parent component. This is a big difference from the Vue update principle. In Vue, the rendering of all components is controlled by their respective data, and the rendering of parent components and child components is independent.

In this article, there are three main parts about React performance optimization.

  • Improve the DOM reuse rate of diff algorithm
  • Reduce resource loading
  • Reduce render times and calculations for components (the most important ones)

Iterate over the list using key

This is related to the React diff algorithm, which is very simple and can be used as an optimization that must comply with the specification.

Add a key value to all traversal lists. This value cannot be a traversal number, but must be a fixed value. For example, the data ID.

This key helps the DIff algorithm reuse DOM elements rather than destroying and regenerating them.

Eliminate unnecessary nodes

The React diff algorithm, like Vue, compares the virtual DOM from parent to child. Therefore, reducing the nesting of nodes can effectively reduce the calculation amount of diff algorithm.

<div className="root">
  <div>
    <h1>My name: {name}</h1>
  </div>
  <div>
    <p>My profile: {content}</p>
  </div>
</div>// Can be reduced to<div className="root">
  <h1>My name: {name}</h1>
  <p>My profile: {content}</p>
</div>
Copy the code

To streamline the state

You don’t need to put all state in a component’s state; only data that needs to be responsive should be stored in state.

Do not use CSS inline styles

React handles three styles

  • css Module
  • Styled CSS in JS (styled- Components)
  • Inline CSS (write styles in component styles)

Both CSS Modules and CSS in JS have their advantages and disadvantages. Although many people say that CSS Module performance is better than CSS in JS, that point of performance is really not worth mentioning.

In this case, inline CSS, if you don’t have the need to change the content or style of the component by controlling the style, don’t write it.

This will be discussed later in the render optimization.

Use useMemo to reduce double counting

Let’s look at an example

import React from 'react';

export default function App() {
  const [num, setNum] = useState(0);
  
  const [factorializeNum, setFactorializeNum] = useState(5);

  // The factorial function
  const factorialize = (): Number= > {
    console.log('triggered');
    let result = 1;
    for (let i = 1; i <= factorializeNum; i++) {
      result *= i;
    }
    return result;
  };

  return (
    <>
      {num}
      <button onClick={()= >SetNum (num + factorialize())}> Modify num</button>
      <button onClick={()= >SetFactorializeNum (factorializeNum + 1)}> Modify factorial parameters</button>
    </>
  );
}
Copy the code

In this component, every time you click on the modify num button, it prints once and the factorial function is recalculated. But the parameters are unchanged, and so are the returns.

We can use useMemo to cache the results and avoid double-counting.

import React, { useMemo } from 'react';

export default function App() {
  const [num, setNum] = useState(0);
  
  const [factorializeNum, setFactorializeNum] = useState(5);

  // When the factorializeNum value does not change, this function does not fire again
  const factorialize = useMemo((): Number= > {
    console.log('triggered');
    let result = 1;
    for (let i = 1; i <= factorializeNum; i++) {
      result *= i;
    }
    return result;
  }, [factorializeNum]);

  return (
    <>
      {num}
      <button onClick={()= >SetNum (num + factorialize())}> Modify num</button>
      <button onClick={()= >SetFactorializeNum (factorializeNum + 1)}> Modify factorial parameters</button>
    </>
  );
}
Copy the code

Use && or ternary expressions

We often encounter this requirement when writing components, rendering different components according to their parameters. case

const App = () = > {
  const [type, setType] = useState(1);

  if (type === 1) {
    return (
      <>
        <Component1>component1</Component1>
        <Component2>component2</Component2>
        <Component3>component3</Component3>
      </>
    );
  }

  return (
    <Component2>component2</Component2>
    <Component3>component3</Component3>
  );
};
Copy the code

The above code looks fine at first glance, returning different components depending on the type. But for the diff algorithm, it will compare the old and new components of the same class, and when the type changes, Component1 is not generated, and for the diff algorithm, it will compare the old first Component1 with the new first Component2, because there is no key, And this is a component, so the diff algorithm goes down to the child elements of the component and does peer comparisons. Assuming that all three components are different, the diff algorithm destroys all three components of the old node and creates two new components.

But in terms of performance, you just destroy the first component and reuse the remaining two.

Add a key, of course, but we can do it in a simpler way.

<>
  {type === 1 && <Component1>component1</Component1>}
  <Component2>component2</Component2>
  <Component3>component3</Component3>
</>
Copy the code

When the type does not conform to the time, · Component1 position will place a NULL, diFF algorithm will take the null and the old Component1 comparison, the remaining two components in the same order, diFF algorithm will reuse. And this way, the code is much more streamlined.

Asynchronous components (lazy-loaded components)

The most typical scenario is TAB page switching. When the TAB is switched to the corresponding page, the js component of the corresponding page is loaded.

These component resources are not included in the main package, and the relevant component JS resources can be loaded later when the user needs them. It can improve the page loading speed and reduce the loading of invalid resources.

Suspense and react. lazy are the two main methods used

import React from 'react';

export default (props) => {
  return (
    <>
      <Drawer>
        <Tabs defaultActiveKey="1">
          <TabPane>
            <React.Suspense fallback={<Loading />}>
              {React.lazy(() => import('./Component1'))}
            </React.Suspense>
          </TabPane>
          <TabPane>
            <React.Suspense fallback={<Loading />}>
              {React.lazy(() => import('./Component2'))}
            </React.Suspense>
          </TabPane>
        </Tabs>
      </Drawer>
    </>
  );
};
Copy the code

After using the above method, Webpack packs the imported component into a single JS. When the TAB switches to the corresponding page, load the JS and render the corresponding component.

Reduce render on components (emphasis)

Use the React. Memo

Let’s start with an example

import React from 'react';

const Child = () = > {
  console.log('Trigger Child component render');
  return (
    <h1>This is the render content of the Child component!</h1>)};export default() = > {const [num, setNum] = useState(0);
  
  return (
    <>
      {num}
      <button onClick={()= >SetNum (num + 1)}>num + 1</button>
      <Child />
    </>
  );
}
Copy the code

Every time we click on the num + 1 button, we will find a trigger Child component rendering printed on the console. The Child component is rerendered each time the state of our parent component changes.

We can use react. memo to avoid repeating render for the child components.

import React from 'react';

const Child = React.memo(() = > {
  console.log('Trigger Child component render');
  return (
    <h1>This is the render content of the Child component!</h1>)});export default() = > {const [num, setNum] = useState(0);
  
  return (
    <>
      {num}
      <button onClick={()= >SetNum (num + 1)}>num + 1</button>
      <Child />
    </>
  );
}
Copy the code

React.memo will determine if the props of the child component have changed, and if not, render will not be repeated. When we hit num plus 1, the Child will not repeat the rendering.

Do not use inline objects directly

Let’s look at another example

import React from 'react';

const Child = React.memo((props) = > {
  const { style } = props;
  console.log('Trigger Child component render');
  return (
    <h1 style={style}>This is the render content of the Child component!</h1>)});export default() = > {const [num, setNum] = useState(0);
  
  return (
    <>
      {num}
      <button onClick={()= >SetNum (num + 1)}>num + 1</button>
      <Child style={{color: 'green'}} / >
    </>
  );
}
Copy the code

This compares to the previous example, which passed an extra style parameter to the Child component. The argument passed in is a static object. Do you think the child component will be rendered repeatedly now?

At first I didn’t think it would, but when I actually tested it, I found that the child component started repeating rendering again.

When the state changes and the parent component is rerendered, {color: ‘green’} is regenerated and the object’s memory address becomes a new one. The React. Memo only compares the props at a superficial level, because the memory address of the object passed in has been changed, so the React. Memo rerenders the child components, assuming the new changes are being made to the props.

We can modify it in two ways.

// If the parameters passed in are completely independent without any coupling
// This parameter can be extracted out of the rendering function
const childStyle = { color: 'green' };
export default() = > {const [num, setNum] = useState(0);
  
  return (
    <>
      {num}
      <button onClick={()= >SetNum (num + 1)}>num + 1</button>
      <Child style={childStyle}/>
    </>
  );
}
// If the parameters passed in need of the parameters or methods in the render function
// You can use useMemo
export default() = > {const [num, setNum] = useState(0);
  const [style, setStyle] = useState('green');
  // If no arguments are required
  const childStyle = useMemo(() = > ({ color: 'green' }), []);
  // If you need to use state or method
  const childStyle = useMemo(() = > ({ color: style }), [style]);
  return (
    <>
      {num}
      <button onClick={()= >SetNum (num + 1)}>num + 1</button>
      <Child style={childStyle}/>
    </>
  );
}
Copy the code

The function passed to the component uses React. UseCallback

The function causes the child to re-render in the same way as the inline object described above. The memory address of the function method changes due to the re-rendering of the parent component, so the react. Memo will assume that the props changed, causing the child to re-render.

We can use React. UseCallback to cache function methods and avoid repeated rendering of child components.

export default() = > {const [num, setNum] = useState(0);
  const oneFnc = useCallback(() = > {
    console.log('This is how we pass in the child.'); } []);return (
    <>
      {num}
      <button onClick={()= >SetNum (num + 1)}>num + 1</button>
      <Child onFnc={oneFnc} />
    </>
  );
}
Copy the code

Similarly, avoid writing anonymous functions directly on the passing parameters of child components.

// Do not write anonymous functions directly<Child onFnc={()= >Console. log(' This is the method passed in child ')} />Copy the code

Use children to avoid repeated rendering of the React Context child component

For our common Context, we can not only use react. Memo to avoid repeated rendering of child components, we can also use children.

import React, { useContext, useState } from 'react';

const DemoContext = React.createContext();

const Child = () = > {
  console.log('Trigger Child component render');
  return (
    <h1 style={style}>This is the render content of the Child component!</h1>)};export default() = > {const [num, setNum] = useState(0);
  return (
    <DemoContext.Provider value={num}>
      <button onClick={()= >SetNum (num + 1)}>num + 1</button>
      <Child />{... Some other components that require the num argument}</DemoContext.Provider>
  );
}
Copy the code

You can use the children method here to avoid duplicate rendering of the Child.

import React, { useContext, useState } from 'react';

const DemoContext = React.createContext();

const Child = () = > {
  console.log('Trigger Child component render');
  return (
    <h1 style={style}>This is the render content of the Child component!</h1>)};function DemoComponent(props) {
  const { children } = props;
  const [num, setNum] = useState(0);
  return (
    <DemoContext.Provider value={num}>
      <button onClick={()= >SetNum (num + 1)}>num + 1</button>
      {children}
    </DemoContext.Provider>
  );
}

export default() = > {return (
    <DemoComponent>
      <Child />{... Some other components that require the num argument}</DemoComponent>
  );
}
Copy the code

At this point, change the state to render only the DemoComponent internally, not the external Child.

conclusion

These are all real problems I have encountered during the development of React, and I believe that all React developers will encounter the problems. The technology involved is not deep, I hope to help some students who are new to React.

Thank you

Thank you for reading, if you feel helpful, please help to click a like support!