• Using PureComponent
  • Use the React. Memo (myComponent, areEqual)
  • Use the shouldComponentUpdate lifecycle event
  • Use React Fragments to avoid extra markup
  • Avoid using inline function definitions
  • Iterate with unique keys
  • Create an error boundary for the component
  • Fine-grained rendering of responsive data
  • Reduce the number of render nodes/render complexity

1. Use PureComponent

It is the same as a normal component except that it is responsible for shouldComponentUpdate, which makes a shallow comparison of the status and props data. The component does not rerender if the previous state and props data is the same as the next props or state.

What is shallow comparison?

When comparing the previous props and state with the next props and state, the shallow comparison checks to see if their properties have the same value, such as whether the values of simple data types are equal or whether the reference addresses of complex data types are equal. This way, component rendering can be skipped and component performance can be improved.

Use React.memo(myComponent, areEqual)

React. Memo is an API used to create functional high-level components. It is similar to PureComponent. The default is to make a shallow comparison of the props input, and skip component rendering if they are the same.

class RegularChildComponent extends React.Component {
  render() {
    console.log("Regular Component Rendered..");
    return <div>{this.props.name}</div>;
  }
}

class PureChildComponent extends React.PureComponent {
  // Pure Components are the components that do not re-render if the State data or props data is still the same
  render() {
    console.log("Pure Component Rendered..")
    return<div>{this.props.name}</div>; }}function CustomisedComponent(props) {
  console.log("Memo Component Rendered..")
  return (
    <div>
      <b>User name: {props.name}</b>
    </div>
  )
}
 
// The component below is the optimised version for the Default Componenent
// The Component will not re-render if same props value for "name" property 
const MemoComponent = React.memo(CustomisedComponent);

class ApplicationComponent extends React.Component {

  constructor() {
    super();
    this.state = {
      name: "Mayank"
    }
  }

  updateState = () => {
    setInterval(() => {
      this.setState({
        name: "Mayank"}})}, 1000)componentDidMount() {
    this.updateState();
  }

  render() {
    console.log("Render Called Again")
    return (
      <div>
        <CustomisedComponent name={this.state.name} />
        <PureChildComponent name={this.state.name} />
        <MemoComponent name={this.state.name} />
      </div>
    )
  }
}
Copy the code

Note:

In contrast to the class component shouldComponentUpdate() method, areEqual returns true if props areEqual; If props are not equal, return false. This is the opposite of the value returned by the shouldComponentUpdate method.

3. Use shouldComponentUpdate Lifecycle events

shouldComponentUpdate(nextProps, nextState) {
  if(nextState.age ! = this.state.age || netState.name === this.state.name) {return true;
  }
  return false;
}
Copy the code

This lifecycle hook takes nextState and nextProps as input and compares them to the current props and state to determine whether to re-render, typically to optimize for some particular business scenario.

4. Use React Fragments to avoid extra markup

When a user creates a new component, each component should have a single parent label. The parent cannot have two tags, so there must be a public tag at the top. So we often add extra tags at the top of components, for example

  render() {
    return(<div>
      <h1>ComponentA</h1>
      <p>aaaaaa</p>
    </div>)
  }
} 
export default ComponentA
Copy the code

This extra div has no purpose other than to act as the parent tag of the component. A better approach is to use a fragement instead of a div, because the code above the fragement has no additional markup, thus saving the renderer the effort to render the additional elements.

  render() {
    return(<>
      <h1>ComponentA</h1>
      <p>aaaaaa</p>
    </>)
  }
} 
export default ComponentA
Copy the code

5. Avoid using inline function definitions

With inline functions, a new instance of the function is created each time the render function is called, which causes the component to always be rerendered.

But sometimes we are forced to use the arrow function as the event handler, as shown in the following scenario.

HandleClick = () => {// handleClick business};render() {
  return (<>{
    listData.map(item => {
      return (<ListItem key={item.id} onClick={event => this.handleClick(event, item)}/>)
    })  
  }</>)
}
Copy the code

Here’s a better way to do it.

HandleClick = () => {// handleClick business};render() {
  return (<>{
    listData.map(item => {
      return (<ListItem key={item.id} id={item.id} onClick={this.handleClick}/>)
    })  
  }</>)
}
Copy the code

What about third-party components or DOM components? This can be done by passing the data-* attribute.

HandleClick = () => {// handleClick business};render() {
  return (<>{
    listData.map(item => {
      return (<ListItem data-key={item.id} data-id={item.id} onClick={this.handleClick}/>)
    })  
  }</>)
}
Copy the code

In passing, there are several practical ways to analyze the binding function context.

  • 1. Bind in Constructor.
constructor() {
  this.handleClick = this.handleClick.bind(this)
}
Copy the code
  • 2. Bind inline functions.
handleClick() {// handle click business}render() {
  return (<>{
    listData.map(item => {
      return (<ListItem data-key={item.id} data-id={item.id} onClick={this.handleClick.bind(this)}/>)
    })  
  }</>)
}
Copy the code
  • 3. Use arrow functions.
HandleClick = () => {// handle clicks}render() {
  return (<>{
    listData.map(item => {
      return (<ListItem data-key={item.id} data-id={item.id} onClick={this.handleClick}/>)
    })  
  }</>)
}
Copy the code

Compared with 1 and 2, the second method binds the context of the function once every time it is rendered, whereas 1 binds only once in the constructor and never again.

Compared with 1 and 3, the third method avoids writing the binding context and uses the arrow function to implicitly bind the context of the current environment. Of course, this method has the disadvantage that each component will have an instance of the test1 function, which affects reusability. Also, because it is an object property but not a stereotype property, the test1 function is not available in the inheritance chain.

class ComponentA extends React.Component {
  constructor() {
    super()
    this.test2 = this.test2.bind(this)
    console.log(this)
  }
  test2() {
    console.log('test1')}test1 = () => {
    console.log('test1')}render() {
    return(<div>
      <h1>ComponentA</h1>
      <p>aaaaaa</p>
    </div>)
  }
} 
Copy the code

To sum up: If the reusability of the component is not high, the form of arrow function can be considered; if the reuse rate is high, it is more recommended to bind in Constructor.

6. Iterate with unique keys

We can use index as the key in the following scenarios:

  • List items are static; items do not change over time.
  • Items do not have a unique ID.
  • Lists are never reordered or filtered.
  • Items are not added or removed from the top or middle.

In other cases, you can consider some hash function that generates the ID.

7. Create error boundaries for components

The error bounds involve a higher-order component and contain the following methods: Static getDerivedStateFromError() and componentDidCatch().

The static function is used to specify the fallback mechanism and to get the new state of the component from the received error.

The componentDidCatch function is used to record error messages to the application.

class ErrorBoundaries extends React.Component {
  constructor(props) {
      super(props);
        this.state = {
        hasErrors: false
      }
  }
 
  componentDidCatch(error, info) {
    console.dir("Component Did Catch Error");
  }
 
  static getDerivedStateFromError(error) {
    console.dir("Get Derived State From Error");
      return {
        hasErrors: true}}render() {
    if(this.state.hasErrors === true) {
        return <div>This is a Error</div>
    }
    return <div><ShowData name="Mayank" /></div>
    }
}
Copy the code
class ShowData extends React.Component {
 
  constructor() {
    super();
    this.state = {
      name: "Mayank"
    }
  }
 
  changeData = () => {
    this.setState({
      name: "Anshul"})}render() {
    if(this.state.name === "Anshul") {
        throw new Error("Sample Error")}return (
      <div>
        <b>This is the Child Component {this.state.name}</b>
        <input type="button" onClick={this.changeData} value="Click To Throw Error" />
      </div>
    )
  }
}
Copy the code

The above code throws an error when the name is updated to Anshul.

The ShowData component is an embedding within the ErrorBoundaries component.

Therefore, if an error is thrown from within the ShowData function, it is caught by the parent component, and we deploy the fallback UI using the static getDerivedStateFromError function and log data from the componentDidCatch lifecycle event.

8. Fine rendering of responsive data

In most cases, responsive data can render a view in fine detail, but it can’t avoid writing inefficient programs. Essentially, it’s because the component violates the ‘single responsibility ‘.

For example, there is now A MyComponent component that relies on data sources A, B, and C to build A VDOM tree. Now what’s the problem? Now any change in A, B, or C will render the entire MyComponent again:

A better approach would be to have a component with a single responsibility and a refined reliance on, or ‘isolation’ of, responsive data. As shown in the figure below, A, B and C are all extracted from their respective components. Now, A change in A will only render A itself, without affecting the parent component and B and C components:

9. Fewer nodes/render computations

  • Do not make unnecessary calculations in all render functions

For example, do not do array sorting, data conversion and so on in render.

  • Immutable data Immutable data makes state predictable and makes shouldComponentUpdate ‘shallow comparison’ more reliable and efficient.

Related tools include immutable.js, Immer, immutability-helper, and Seamless – Immutable.

  • Virtual list

Virtual lists are common ‘long lists’ and’ complex component trees’ optimizations, and their essence is to reduce the number of nodes rendered.

Virtual lists are used in the following component scenarios:

Infinite scrolling list, table, drop – down list

The recommended react-Virtualized, antD List component also has an infinite scrolling List based on this component

  • Lazy loading of components

We can load these discrete components lazily on demand to enhance the overall performance of the application. Suppose you have two components, ComponentA or ComponentB, and render one of them based on the identity. We can defer component loading depending on specific conditions, rather than loading two components at the beginning.

const LazyComponentA = lazy(() => slowImport(import("./components/ComponentA")));
const LazyComponentB = lazy(() => slowImport(import("./components/ComponentB")));
import ComponentA from './components/ComponentA'
import ComponentB from './components/ComponentB'// Simulate delayed loadingfunction slowImport(value, ms = 1000){
  return new Promise(resolve=>{
    setTimeout(() => resolve(value), ms); })}export default () => {
  const [isShowA, setIsShowA] = useState(true)
  setTimeout(() => {
    setIsShowA(false)}, 10000)return (
    <PageHeaderWrapper content="React Performance optimization"> { isShowA ? (<> <Suspense fallback={<h1>Loading... </h1>}> <LazyComponentA /> </Suspense> </>) : (<> <Suspense fallback={<h1>Loading... </h1>}> <LazyComponentB /> </Suspense> </>) } { isShowA ? <ComponentA /> : <ComponentB /> } </PageHeaderWrapper> ); };Copy the code

In this way, the main packet size can be reduced, the network transmission time is consumed less, and the dynamic loading of separate packets is smaller, which can be quickly loaded. We can analyze the application to determine which components to load lazily, thereby reducing the initial load time of the application. Real project scenarios can be tabs, tree selectors, modal pop-ups, drop – down lists, collapsed components, and so on