Eight ways to optimize React performance

Posted by dotMoe on Fri, 24 Dec 2021 11:04:48 +0100

React has efficient performance by virtue of virtual DOM and diff algorithm. In addition, there are many other methods and techniques that can further improve react performance. In this paper, I will list several methods that can effectively improve react performance to help us improve react code and performance. But we don't have to use these methods in the project, but we need to know how to use them.

Use react Memo to cache components

One way to improve application performance is to implement memoization. Memoization is an optimization technology, which mainly speeds up the program by storing the results of expensive function calls and returning the cached results when the same input occurs again.

Each state update of the parent component will cause the child component to re render, even if the state of the incoming child component does not change. In order to reduce repeated rendering, we can use react Memo to cache components, so that they will be re rendered only when the state value of the incoming component changes. If the same value is passed in, the cached component is returned. Examples are as follows:

export default React.memo((props) => {
  return (
    <div>{props.value}</div>  
  )
});

Use useMemo to cache a large number of calculations

Sometimes rendering is inevitable, but if your component is a functional component, re rendering will cause a large calculation function to be called every time, which is very performance consuming. We can use the new useMemo hook to "remember" the calculation results of this calculation function. In this way, the calculation function will be called again to calculate the new result only after the incoming parameters change.

In this way, you can save expensive computing time by using the results of calculations from previous rendering. The overall goal is to reduce the amount of work that JavaScript must perform during rendering components so that the main thread is blocked for a shorter time.

// Avoid doing so
function Component(props) {
  const someProp = heavyCalculation(props.item);
  return <AnotherComponent someProp={someProp} /> 
}
  
// Only ` props The value of someProp will be recalculated only when item ` is changed
function Component(props) {
  const someProp = useMemo(() => heavyCalculation(props.item), [props.item]);
  return <AnotherComponent someProp={someProp} /> 
}

Use react PureComponent , shouldComponentUpdate

Each update of the parent component status will lead to the re rendering of child components, even if the same props are passed in. However, the re rendering here does not mean that the DOM will be updated, but the diif algorithm will be called every time to determine whether the DOM needs to be updated. This is very performance consuming for large components, such as component trees.

Here we can use react Purecomponent, shouldcomponentupdate lifecycle to ensure that the component will be re rendered only when its props state changes. Examples are as follows:

export default function ParentComponent(props) {
  return (
    <div>
      <SomeComponent someProp={props.somePropValue}
    <div>
      <AnotherComponent someOtherProp={props.someOtherPropValue} />
    </div>
   </div>
 )
}
 
export default function SomeComponent(props) {
  return (
    <div>{props.someProp}</div>  
  )
}
 
// Just props Somepropvalue changes regardless of props Whether someotherpropvalue changes or not, the component will change
export default function AnotherComponent(props) {
  return (
    <div>{props.someOtherProp}</div>  
  )
}

We can use react Purecomponent or shouldComponentUpdate is optimized as follows:

// First optimization
class AnotherComponent extends React.PureComponent {
  render() {
    return <div>{this.props.someOtherProp}</div>   
  }
}
 
//Second optimization
class AnotherComponent extends Component {
  shouldComponentUpdate(nextProps) {
    return this.props !== nextProps
  }
  render() {
    return <div>{this.props.someOtherProp}</div>   
  }
}

PureComponent will make a shallow comparison to determine whether the component should be re rendered. For the incoming basic type props, as long as the values are the same, the shallow comparison will consider it the same. For the incoming reference type props, the shallow comparison will only consider whether the incoming props are the same reference. If not, even if the contents of the two objects are exactly the same, It will also be considered different props.

It should be noted that for components whose rendering time can be ignored or whose state changes all the time, PureComponent should be used with caution, because shallow comparison will also take time, Front end training This optimization is more suitable for large display components. Large components can also be split into multiple small components and wrapped with memo, which can also improve performance.

Avoid inline objects

When using an inline object, react will recreate the reference to this object every time it is rendered, which will cause the component receiving this object to treat it as a different object. Therefore, the component always returns false for the shallow comparison of prop, resulting in the component always being re rendered.

Indirect references to inline styles used by many people will re render components, which may lead to performance problems. To solve this problem, we can ensure that the object is initialized only once and points to the same reference. Another case is to pass an object, which will also create different references during rendering, which may also lead to performance problems. We can use ES6 extension operator to deconstruct the passed object. In this way, the component receives the basic type of props. If the component finds that the accepted props have not changed through shallow comparison, it will not be re rendered. Examples are as follows:

// Don't do this!
function Component(props) {
  const aProp = { someProp: 'someValue' }
  return <AnotherComponent style={{ margin: 0 }} aProp={aProp} />  
}
 
// Do this instead :)
const styles = { margin: 0 };
function Component(props) {
  const aProp = { someProp: 'someValue' }
  return <AnotherComponent style={styles} {...aProp} />  
}

Avoid anonymous functions

Although anonymous functions are a good way to pass functions (especially functions that need to be called with another prop as a parameter), they have different references on each rendering. This is similar to the inline object described above. In order to maintain the same reference to the function passed to the React component as prop, you can declare it as a class method (if you are using class based components) or use the useCallback hook to help you keep the same references (if you are using functional components).

Of course, sometimes inlining anonymous functions is the easiest way, and it doesn't actually cause performance problems in the application. This may be because it is used on a very "lightweight" component, or because the parent component actually has to re render all its contents every time props changes. Therefore, it doesn't matter whether the function is a different reference, because the component will be re rendered anyway.

// Avoid doing so
function Component(props) {
  return <AnotherComponent onChange={() => props.callback(props.id)} />  
}
 
// Optimization method I
function Component(props) {
  const handleChange = useCallback(() => props.callback(props.id), [props.id]);
  return <AnotherComponent onChange={handleChange} />  
}
 
// Optimization method II
class Component extends React.Component {
  handleChange = () => {
   this.props.callback(this.props.id) 
  }
  render() {
    return <AnotherComponent onChange={this.handleChange} />
  }
}

Deferred loading is not an immediate requirement

Deferred loading is virtually invisible (or not immediately needed) components. The fewer components that React loads, the faster it loads. Therefore, if your initial rendering feels rather rough, you can reduce the number of loaded components by loading components when needed after the initial installation. At the same time, this will allow users to load your platform / application faster. Finally, by splitting the initial installation Rendering, you split the JS workload into smaller tasks, which will provide response time for your page. This can be done using the new React Lazy and React Suspend is easy.

// Deferred loading is not an immediate requirement
const MUITooltip = React.lazy(() => import('@material-ui/core/Tooltip'));
function Tooltip({ children, title }) {
  return (
    <React.Suspense fallback={children}>
      <MUITooltip title={title}>
        {children}
      </MUITooltip>
    </React.Suspense>
  );
}
 
function Component(props) {
  return (
    <Tooltip title={props.title}>
      <AnotherComponent />
    </Tooltip>
  )
}

Adjust CSS instead of forcing components to load and unload

Rendering is expensive, especially when you need to change the DOM. Whenever you have some accordion or tag function, for example, you want to see only one item at a time, you may want to uninstall the invisible component and reload it when it becomes visible. If the loaded / unloaded component is "heavy", this operation may be very performance consuming and may cause delays. In these cases, it's best to hide it through CSS and save the content to the DOM. Front end training

Although this method is not omnipotent, Because installing these components may cause problems (that is, the components compete with unlimited paging on the window), but we should choose to use the method of adjusting CSS in this case. In addition, adjusting the opacity to 0 will almost cost the browser 0 (because it will not cause rearrangement), and should take precedence over the visibility and display as much as possible.

Sometimes it may be beneficial to hide through CSS while keeping components loaded, rather than by unloading. This is an effective means of performance optimization for heavy components with significant load / unload timing.

// Avoid frequent loading and unloading of large components
function Component(props) {
  const [view, setView] = useState('view1');
  return view === 'view1' ? <SomeComponent /> : <AnotherComponent />  
}
// Use this method to improve performance and speed
const visibleStyles = { opacity: 1 };
const hiddenStyles = { opacity: 0 };
function Component(props) {
  const [view, setView] = useState('view1');
  return (
    <React.Fragment>
      <SomeComponent style={view === 'view1' ? visibleStyles : hiddenStyles}>
      <AnotherComponent style={view !== 'view1' ? visibleStyles : hiddenStyles}>
    </React.Fragment>
  )
}

Use react Fragment avoids adding extra DOM

In some cases, we need to return multiple elements in the component, such as the following elements, but react stipulates that there must be a parent element in the component.

<h1>Hello world!</h1>
<h1>Hello there!</h1>
<h1>Hello there again!</h1>

So you may do this, but doing so will create additional unnecessary div even if everything is normal. This results in the creation of many useless elements throughout the application:

function Component() {
        return (
            <div>
                <h1>Hello world!</h1>
                <h1>Hello there!</h1>
                <h1>Hello there again!</h1>
            </div>
        )
}

In fact, the more elements on the page, the more time it takes to load. In order to reduce unnecessary loading time, we can make react Fragment to avoid creating unnecessary elements.

function Component() {
        return (
            <React.Fragment>
                <h1>Hello world!</h1>
                <h1>Hello there!</h1>
                <h1>Hello there again!</h1>
            </React.Fragment>
        )
}

summary

Basically, the performance optimization methods provided by React are listed in this article. These methods can help React perform better. For example, immutable JS third-party tool library optimization method. In fact, there are many methods of performance optimization, but as mentioned above, appropriate methods should also be used in appropriate scenarios. Excessive use of performance optimization will not pay off.