The core of React component performance optimization is to reduce the frequency of rendering real DOM nodes and reducing the frequency of Virtual DOM alignment.

1. Clear components before uninstalling them

Global events and timers registered for Windows in components must be cleared before component uninstallation to prevent component execution from affecting application performance.

Need: Use useState to save a value, then start the timer to change the value, uninstall the component to see if the timer is still running.

import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';

const App = () = > {
  const [count, setCount] = useState(0);
  useEffect(() = > {
    let timer = setInterval(() = > {
      setCount(prev= > prev + 1);
    }, 1000);
    return () = > {
      clearInterval(timer); }} []);return (
    <button onClick={()= > ReactDOM.unmountComponentAtNode(document.getElementById('root'))}>
      {count}
    </button>)}export default App;
Copy the code

2. PureComponent

  1. What is a pure component

A pure component makes a shallow comparison of component input data, and if the current input data is the same as the last input data, the component does not re-render.

  1. What is shallow comparison

Compares whether the reference data types have the same address in memory, and compares whether the values of the base data types are the same.

  1. How to implement pure components

The class component inherits PureComponent and the function component uses the Memo method.

  1. Why not just do the diff operation, but do the shallow comparison first. Isn’t there a performance cost to shallow comparison?

Shallow comparisons consume less performance than diff comparisons. The diff operation retraverses the entire virtualDOM tree, and the shallow comparison only operates on the state and props of the current component.

Requirement: Store count as 1 in the state object, change the count property value to 1 again after the component is mounted, and then pass count to pure and impure components, respectively.

class App extends React.Component {
  constructor (props) {
    super(a);this.state = {
      count: 1
    }
  }

  componentDidMount () {
    this.setState({count: 1})
  }

  render () {
    return (
      <div>
        <RegularChildComponent count={this.state.count}/>
        <PureChildComponent count={this.state.count}/>
      </div>)}}class RegularChildComponent extends React.Component {
  render () {
    console.log('RegularChildComponent render');
    return <div>{this.props.count}</div>}}class PureChildComponent extends React.PureComponent {
  render () {
    console.log('PureChildComponent render');
    return <div>{this.props.count}</div>}}export default App;
Copy the code

Console output

RegularChildComponent render
PureChildComponent render
RegularChildComponent render
Copy the code

3. shouldComponentUpdate

Pure components can only be compared at a shallow level. To do a deep comparison, use shouldComponentUpdate, which is used to write custom logic.

Returns true to rerender the component and false to prevent rerendering.

The first argument to the function is nextProps and the second argument is nextState.

Requirements: display employee information on the page, including name, age, position. But only the name and age are displayed on the page. This means it is only necessary to re-render the component if the name and age have changed, not if other information about the employee has changed.

import React from "react"

export default class App extends React.Component {
  constructor() {
    super(a)this.state = {name: "Zhang".age: 20.job: "waiter"}}componentDidMount() {
    setTimeout(() = > this.setState({ job: "chef" }), 1000)}shouldComponentUpdate(nextProps, nextState) {
    if (this.state.name ! == nextState.name ||this.state.age ! == nextState.age) {return true
    }
    return false
  }

  render() {
    console.log("rendering")
    let { name, age } = this.state
    return <div>{name} {age}</div>}}Copy the code

4. React.memo

Basic Use of memo

Make the function component pure, compare the current props to the previous props, and prevent the component from rerendering if they are the same.

Requirement: The parent component maintains two states, index and name. Start timer to make index change continuously. Name is passed to the child component to check whether the parent component updates the child component.

import React, { memo, useEffect, useState } from "react"
   
function ShowName({ name }) {
 console.log("showName render...")
 return <div>{name}</div>
}

const ShowNameMemo = memo(ShowName)

function App() {
 const [index, setIndex] = useState(0)
 const [name] = useState("Zhang")
 useEffect(() = > {
   setInterval(() = > {
     setIndex(prev= > prev + 1)},1000)}, [])return (
   <div>
     {index}
     <ShowNameMemo name={name} />
   </div>)}export default App
Copy the code

Compare logic for memo passing

Customize the comparison logic using the Memo method to perform deep comparisons.

The first argument to the comparison function is props for the previous one, and the second argument is props for the next one. The comparison function returns true, false without rendering, and the component is re-rendered.

import React, { memo, useEffect, useState } from "react"
   
function ShowPerson({ person }) {
 console.log("ShowPerson render...")
 return (
   <div>
     {person.name} {person.age}
   </div>)}function comparePerson(prevProps, nextProps) {
 if( prevProps.person.name ! == nextProps.person.name || prevProps.person.age ! == nextProps.person.age ) {return false
 }
 return true
}
   
const ShowPersonMemo = memo(ShowPerson, comparePerson)
   
function App() {
 const [person, setPerson] = useState({ name: "Zhang".age: 20.job: "waiter" })
 useEffect(() = > {
   setTimeout(() = >{ setPerson({ ... person,job: "chef"})},1000)}, [])return <ShowPersonMemo person={person} />
}

export default App
Copy the code

5. Use lazy loading of components

Using lazy component loading can reduce bundle file size and speed up component rendering.

1. Route components are loaded lazily

import React, { lazy, Suspense } from "react"
import { BrowserRouter, Link, Route, Switch } from "react-router-dom"

const Home = lazy(() = > import(/* webpackChunkName: "Home" */ "./Home"))
const List = lazy(() = > import(/* webpackChunkName: "List" */ "./List"))

function App() {
 return (
   <BrowserRouter>
     <Link to="/">Home</Link>
     <Link to="/list">List</Link>
     <Switch>
       <Suspense fallback={<div>Loading</div>} ><Route path="/" component={Home} exact />
         <Route path="/list" component={List} />
       </Suspense>
     </Switch>
   </BrowserRouter>)}export default App
Copy the code

2. Load components lazily based on conditions

Applies to components that do not switch frequently with conditions

import React, { lazy, Suspense } from "react"
   
function App() {
 let LazyComponent = null
 if (true) {
   LazyComponent = lazy(() = > import(/* webpackChunkName: "Home" */ "./Home"))}else {
   LazyComponent = lazy(() = > import(/* webpackChunkName: "List" */ "./List"))}return (
   <Suspense fallback={<div>Loading</div>} ><LazyComponent />
   </Suspense>)}export default App
Copy the code

6. Use fragments to avoid extra markup

If the JSX returned by the React component has more than one sibling element, the siblings must have a common parent.

function App() {
  return (
    <div>
      <div>message a</div>
      <div>message b</div>
    </div>)}Copy the code

To satisfy this requirement, we usually add a div at the outermost layer, but this creates an extra meaningless tag, and if each component has such a meaningless tag, the browser rendering engine will be burdened.

React addresses this issue by introducing the Fragment placeholder, which satisfies the requirement of having a common parent without creating additional meaningless tags.

import { Fragment } from "react"

function App() {
  return (
    <Fragment>
      <div>message a</div>
      <div>message b</div>
    </Fragment>)}Copy the code
function App() {
  return (
    <>
      <div>message a</div>
      <div>message b</div>
    </>)}Copy the code

Do not use inline function definitions

After using the inline function, the render method creates an instance of the function every time it runs. This results in a mismatch between the old and new functions when React performs a Virtual DOM comparison. As a result, React always binds new functions to elements, and the old function instances have to be handled by the garbage collector.

import React from "react"

export default class App extends React.Component {
  constructor() {
    super(a)this.state = {
      inputValue: ""}}render() {
    return (
      <input
        value={this.state.inputValue}
        onChange={e= > this.setState({ inputValue: e.target.value })}
        />)}}Copy the code

The correct approach is to define separate functions within the component and bind them to events.

import React from "react"

export default class App extends React.Component {
  constructor() {
    super(a)this.state = {
      inputValue: ""
    }
  }
  setInputValue = e= > {
    this.setState({ inputValue: e.target.value })
  }
  render() {
    return (
      <input value={this.state.inputValue} onChange={this.setInputValue} />)}}Copy the code

8. Bind the function this in the constructor

If fn() {} is used to define the event function in a class component, the event function this refers to undefined by default.

You can correct the function’s this pointer in the constructor, or you can correct it in the line, and the two don’t look too different, but the impact on performance is different.

export default class App extends React.Component {
   constructor() {
    super(a)/ / way
     // The constructor is executed only once, so the code pointing to the correction is executed only once.
    this.handleClick = this.handleClick.bind(this)}handleClick() {
    console.log(this)}render() {
    2 / / way
    // Problem: The render method calls bind every time it executes to generate a new instance of the function.
    return <button onClick={this.handleClick.bind(this)}>button</button>}}Copy the code

9. Arrow functions in class components

There is no this pointing problem when using arrow functions in class components, because the arrow functions themselves are not bound to this.

export default class App extends React.Component {
  handleClick = () = > console.log(this)
  render() {
    return <button onClick={this.handleClick}>button</button>}}Copy the code

The arrow function has the advantage of the this pointing problem, but it also has a downside.

When using the arrow function, this function is added as an instance object property of the class, rather than a prototype object property. If the component is reused multiple times, each component instance object will have the same function instance, reducing the reusability of the function instance and resulting in a waste of resources.

10. Avoid inline style attributes

When you style an element using inline style, the inline style is compiled into JavaScript code that maps the style rules to the element, and the browser spends more time executing scripts and rendering the UI, increasing the component’s rendering time.

function App() {
  return <div style={{ backgroundColor: "skyblue}} ">App works</div>
}
Copy the code

In the above component, an inline style is appended to the element. The added inline style is a JavaScript object. The backgroundColor needs to be converted to an equivalent CSS style rule and then applied to the element, which involves the execution of the script.

A better approach is to import the CSS file into the style component. Don’t do anything you can do directly with CSS with JavaScript, which is very slow to manipulate the DOM.

11. Optimize rendering conditions

Mounting and unmounting components frequently is a performance-intensive operation, and to ensure application performance, you should reduce the number of times components are mounted and unmounted.

In React, we often render different components based on different conditions. Conditional rendering is a mandatory optimization operation.

function App() {
  if (true) {
    return (
      <>
        <AdminHeader />
        <Header />
        <Content />
      </>)}else {
    return (
      <>
        <Header />
        <Content />
      </>)}}Copy the code

The React component is AdminHeader, the first component is Header, the second component is Header, and the second component is Content. React unmounts AdminHeader, Header, and Content. This mounting and unmounting is unnecessary.

function App() {
  return (
    <>
      {true && <AdminHeader />}
      <Header />
      <Content />
    </>)}Copy the code

12. Avoid repeating infinite renders

React calls the Render method when the state of the application changes. If you continue to change the state of the application within the Render method, a recursive call to the Render method will result in an application error.

export default class App extends React.Component {
  constructor() {
    super(a)this.state = {name: "Zhang"}}render() {
    this.setState({name: "Bill"})
    return <div>{this.state.name}</div>}}Copy the code

Unlike other lifecycle functions, the Render method should be treated as a pure function, which means that you should not do the following things in the Render method: do not call the setState method, do not use other means to query for changes to native DOM elements, or do anything else that changes the application. The render method is executed according to the state to keep the behavior of the component consistent with how it is rendered.

13. Create error boundaries for components

By default, component rendering errors cause the entire application to break, and creating error boundaries ensures that the application does not break when a particular component fails.

Error bounds is a React component that captures errors that occur in child components during rendering. When errors occur, it logs them and displays alternate UI interfaces.

The error boundary involves two life cycle functions, getDerivedStateFromError and componentDidCatch.

GetDerivedStateFromError is a static method that returns an object that is merged with the State object to change application state.

The componentDidCatch method is used to log application error information, and its arguments are error objects.

import React from "react"
import App from "./App"

export default class ErrorBoundaries extends React.Component {
  constructor() {
    super(a)this.state = {
      hasError: false}}componentDidCatch(error) {
    console.log("componentDidCatch")}static getDerivedStateFromError() {
    console.log("getDerivedStateFromError")
    return {
      hasError: true}}render() {
    if (this.state.hasError) {
      return <div>Error occurred</div>
    }
    return <App />}}Copy the code
import React from "react"

export default class App extends React.Component {
  render() {
    // throw new Error("lalala")
    return <div>App works</div>}}Copy the code
import React from "react"
import ReactDOM from "react-dom"
import ErrorBoundaries from "./ErrorBoundaries"

ReactDOM.render(<ErrorBoundaries />.document.getElementById("root"))
Copy the code

Note: Error bounds do not catch asynchronous errors, such as those that occur when a button is clicked.

14. Avoid data structure mutations

The props and state data structures in the component should be consistent. Data structure mutations may cause inconsistent output

import React, { Component } from "react"

export default class App extends Component {
  constructor() {
    super(a)this.state = {
      employee: {
        name: "Zhang".age: 20}}}render() {
    const { name, age } = this.state.employee
    return (
      <div>
        {name}
        {age}
        <button
          onClick={()= >this.setState({ ... this.state, employee: { ... this.state.employee, age: 30 } }) } > change age</button>
      </div>)}}Copy the code

15. Add a unique identifier to the list data

When rendering list data, we should add a key attribute to each list item. The value of the key attribute must be unique.

The Key property allows React to know directly which list items have changed, avoiding the performance cost of walking through the Virtual DOM one by one and avoiding element re-creation due to positional changes.

When a list item has no unique identifier, you can temporarily use the index as the value of the key attribute, but only if the list item is static and cannot be dynamically changed, such as not sorting or filtering the list item, and not adding or removing items from the top or middle. When the above behavior occurs, the index changes and is not reliable.

import React from "react"

const arrayData = [
  { id: 1.data: "apple" },
  { id: 2.data: "pear"}]function App() {
  return (
    <ul>
      {arrayData.map(item => (
        <li key={item.id}>{item.data}</li>
      ))}
    </ul>)}export default App
Copy the code

16. Dependency optimization

It’s common to rely on third-party packages in your applications, but we don’t want to reference all of the code in the packages; we just want to include the code we use. At this point, dependencies can be optimized using plug-ins. Optimization of resources

We are currently using loDash as an example, where an application is created based on create-React-app scaffolding

  1. Download dependency yarn add react-app-rewired Customize Lodash babel-plugin-lodash

    A. React-app-remired Mired: overwrites the default configuration of create-react-app

    module.exports = oldConfig= > {
      return newConfig;
    }
    // oldConfig is the default Webpack config
    Copy the code

    B. Customize-cra: Some helper methods are exported to make this simpler

    const { override, useBabelRc } = require("customize-cra");
    
    module.exports = override({
      oldConfig= > newConfig,
      oldConfig= > newConfig
    })
    Copy the code
    • Override: Can accept multiple arguments, each of which is a configuration function that receives oldConfig and returns newConfig
    • UseBabelRc: allows you to configure Babel using the. Babelrc file

    C. babel-plugin-loDash: Simplify loDash in the application

  2. Create config-overrides. Js in the root directory of the project and add the configuration code

    const { override, useBabelRc } = require("customize-cra")
    
    module.exports = override(useBabelRc())
    Copy the code
  3. Modify build commands in package.json files

    "scripts": {
     "start": "react-app-rewired start"."build": "react-app-rewired build"."test": "react-app-rewired test --env=jsdom"."eject": "react-scripts eject"
    }
    Copy the code
  4. Create the.babelrc file and add it to the configuration

    {
      "plugins": ["lodash"]}Copy the code
  5. Three kinds of JS files in production environment

    1. Main.[hash].chunk.js: This is your application code, app.js, etc.

    2. 1.[hash].chunk.js: This is the third-party library code that contains the modules you imported in node_modules

    3. Runtime ~main.[hash].js Webpack runtime code

    Initial package size without loDash:

    Package size after adding Lodash:

    Optimized package size for LoDash:

  6. App component

import React from "react"
import _ from "lodash"

function App() {
 console.log(_.chunk(["a"."b"."c"."d"].2))
 return <div>App works</div>
}

export default App
Copy the code