SoFunction
Updated on 2025-04-07

Detailed explanation of the implementation method of React performance optimization

Preface

If you want to write high-quality code, it is far from enough to rely solely on the framework to help us optimize. During the writing process, we need to use the improved API ourselves, or do some optimizations and specifications based on the underlying principles.

Compared with Vue, React will no longer help us directly solve basic performance optimization related problems at the source code level of the framework, but will provide APIs (Hooks) to allow us to optimize our applications by ourselves, which is one of the reasons why it is more flexible.

The following summarizes some optimization points that can be done from the level of writing React code.

Use the key to traverse the view

The role of key can help us identify which elements have changed, such as adding and deleting. When React is updated, the React Diff algorithm will be triggered. During the diff process, the key value is used to determine whether the element is newly created or an element that needs to be moved. React will save this auxiliary state. This reduces unnecessary element rendering.

The value of key is preferably a unique string in the current list. In development, id and other elements are usually used as key values.

There will be no operation in the current list, and you can use index as the key value as the last resort.

The key should have stable, predictable, and unique traits on the list. Unstable keys such as () generated will cause many component instances and DOM nodes to be recreated unnecessary, which may lead to performance degradation and child component state loss, etc.

Cache Components

react is a one-way data stream. The update of the parent component's state will also allow the child component to re-render and update together. Even if the state of the child component has not changed, it will not be able to specifically monitor the change in the state of a certain component and then update the current component like Vue.

Therefore it can be usedCache components, so that they will only be re-rendered when the current component state value changes. If the value is the same, the component will be cached.

// Subcomponentsconst Child = (() => {
  ("child");
  return (
    <div>
      Child
    </div>
  );
});
// Parent componentfunction App() {
  const [count, setCount] = useState(0);
  return (
    <div className="App">
      <h3>{count}</h3>
      <button onClick={() => setCount(count + 1)}>Count++ </button>
      <Child />
    </div>
  );
}

The above code<Child />Component addedmemoEvery time you click count ++, it will not be re-rendered.

Keep the function the same reference

As in the example above, if the parent component wants to get the status value of the child component, it will usually pass it out to the parent component in the form of callback.

interface ChildProps {
  onChangeNum: (value: number) => void;
}
const Child: <ChildProps> = (({ onChangeNum }) => {
  ("child");
  const [num, setNum] = useState(0);
  useEffect(() => {
    onChangeNum(num);
  }, [num]);
  return (
    <div>
      <button
        onClick={() => {
          setNum((prevState) => {
            return prevState + 1;
          });
        }}
      >
        Child
      </button>
    </div>
  );
});
function App() {
  const [count, setCount] = useState(0);
  return (
    <div className="App">
      <h3>{count}</h3>
      <button onClick={() => setCount(count + 1)}>Count++ </button>
      <Child
        onChangeNum={(num) => {
          (num, "childNum");
        }}
      />
    </div>
  );
}

Every time a component updates a num value, the parent component accepts it through onChangeNum function.

Pay attention to what I just saidmemoIt is possible to cache components to avoid re-rendering when the component's incoming value remains unchanged, but this is invalid again. Why is this?

The reason is that the parent component has been updated. A new onChangeNum will be created every time, which is equivalent to a different reference. The re-drop function passed by props is different every time, so memo loses its effect.

So how to solve it? That's useuseCallbackhooks help us keep the same references.

<Child
  onChangeNum={useCallback((num) => {
    (num, "childNum");
  }, [])}
/>

Used in developmentmemoAfter cached components, you also need to pay attention to whether anonymous functions are passed to subcomponents.

UseCallback is not necessarily only used in this case. For example, a request function or logical processing function can also be wrapped with useCallback. However, it should be noted that the internal references external state or value associations, so some values ​​used in the second parameter, that is, the dependency array need to be added.

Avoid using inline objects

When using inline objects, react will recreate this object every time it is rerendered, and when updating component comparison props,oldProps === newPropsJust forfalseThen it willre-render

ifTestComponentThe component is re-rendered, and someProps reference will be created. Pass toRootComponentThe result of the component's new and old props is different every time, resulting in re-rendering.

const TestComponent = () => {
  const someProps = { value: '1' }
  return <RootComponent someProps={someProps} />;
};

A better way is to use the ES6 extension operator to expand the object, change the reference type to the value type to pass, so that the comparison of props will be equal.

const TestComponent = () => {
  const someProps = { value: '1' }
  return <RootComponent {...someProps} />;
};

Use cached calculation results or components

As the React documentation says,useMemoThe basic function is to avoid high overhead calculations for each rendering.

If it is a functional component, large computing is involved, and each time the component is re-rendered, a large computing function is called again, which is very performance-consuming and we can useuseMemoCaches the calculation results of this function to reduce the amount of work that JavaScript must perform during rendering components, and to shorten the time to block the main thread.

// Recalculate it only when the id changesconst TestComponent = () =&gt; {
  const value = useMemo(() =&gt; {
    return expensiveCalculation()
  }, [id]) 
  return &lt;Component countValue={value} /&gt;
}

Before using useMemo to cache the calculation results, you need to apply them in appropriate places. UseMemo is also costly, and it will also increase the time-consuming process of initializing the overall program, unless this calculation is really expensive, such as factorial calculation.

Therefore, it is not suitable for global use, it is more suitable for local optimization. You should not overuseMemo.

In addition, while caching the result value, it can also be used to cache components.

For example, there is a globalcontext, As the context is filled with a lot of states in the long-term project iteration, we know that the value of the context changes, which will cause the component to be re-rendered. This component is a large component that consumes performance and will only be re-rendered by one of the variables. At this time, you can consider using useMemo for caching.

const TestComponent = () => {
  const appContextValue = useContext(AppContext);
  const theme = ;
  return useMemo(() => {
    return <RootComponent className={theme} />;
  }, [theme]);
};

<RootComponent />Only inthemeRe-render when the variable changes.

Use fragments

react specifies that there must be a parent element in the component, but in some cases, the root tag does not require any attributes, which will lead to the creation of many useless elements in the entire application, so the function of this tag is not very meaningful.

const TestComponent = () => {
  return (
    <div>
      <ChildA />
      <ChildB />
      <ChildC />
    </div>
  );
}

In fact, the more elements there are on the page, the deeper the DOM structure is nested, the more time it takes to load, which will also increase the rendering pressure of the browser.

Therefore React providesFragmentComponents instead of wrapping the outer layer, it will not help us create the outer layer additionallydivLabel.

const TestComponent = () => {
  return (
    <>
      <ChildA />
      <ChildB />
      <ChildC />
    </>
  );
}

Or another simple way to use empty tags<></>The same effect is used instead:

const TestComponent = () => {
  return (
    <>
      <ChildA />
      <ChildB />
      <ChildC />
    </>
  );
}

There are also some practical scenarios to render elements according to conditions

const TestComponent = () => {
  const { isLogin, name } = useApp();
  return (
    <>
      {isLogin ? (
        <>
          <h3>Welcome {name}</h3>
          <p>You are logged in!</p>
        </>
      ) : (
        <h3>go login...</h3>
      )}
    </>
  );
};

Lazy component loading

The speed of application initialization loading is also related to the number of components. Therefore, during initialization, some pages that we cannot see, that is, components that cannot be used at the beginning, can choose to delay loading components. What we can think of is lazy loading of routes, so as to improve the loading speed and response time of the page.

react providesandLet's help us implement lazy loading of components.

import React, { lazy, Suspense } from 'react';
const AvatarComponent = lazy(() => import('./AvatarComponent'));
const renderLoader = () => <p>Loading</p>;
const DetailsComponent = () => (
  <Suspense fallback={renderLoader()}>
    <AvatarComponent />
  </Suspense>
)

SuspenseThe function is to make up for itLazyWhat you can do during this blank time before the component is loaded, especially when the component is larger, or in weaker devices and networks, it can be done throughfallbackThe property adds a loading to prompt the user to load the status. The asynchronous component will be displayed after it is loaded.

If used alonelazyReact will issue an error message on the console!

Loading and uninstalling components via CSS

Rendering is expensive, and if the ‘heavy’ components are frequently loaded/unloaded, this operation can be very performance-consuming or cause delays. Under normal circumstances, we will use the ternary operator to judge the loading display, which also leads to a problem. Every time we update frequently and trigger the loading of different components, there will be certain performance losses. At this time, we can use the CSS attribute to hide it so that the DOM can be kept on the page as a heavyweight.

**However, this method is not omnipotent, and may cause some layout or window misalignment. ** But we should choose to use the method of tuning CSS when this is not the case. Another point, adjusting opacity to 0 costs the browser to almost 0 (as it won't cause reordering) and should take precedence over the more visibility and display as much as possible.

// Avoid frequent loading and unloading of large componentsconst ViewExample = () =&gt; {
  const [isTest, setIsTest] = useState(true)
  return (
    &lt;&gt;
      { isTest ? &lt;ViewComponent /&gt; : &lt;TestComponent /&gt;}
    &lt;/&gt;
  );
};
// Use this method to improve performance and speedconst visibleStyles = { opacity: 1 };
const hiddenStyles = { opacity: 0 };
const ViewExample = () =&gt; {
  const [isTest, setIsTest] = useState(true)
  return (
    &lt;&gt;
      &lt;ViewComponent style={!isTest ? visibleStyles : hiddenStyles} /&gt; 
			&lt;TestComponent style={{ isTest ? visibleStyles : hiddenStyles }} /&gt;
    &lt;/&gt;
  );
};

Separate from the unchanging place

Usually, useMemo and useCallback are used for optimization. Here we say that we do not use these Hooks for optimization.

The concept of separation between change and invariance is actually because of its own react mechanism. The state of the parent component is updated, and all child components must be rendered together, which means to separate stateful components and stateless components.

function ExpensiveCpn() {
  ("ExpensiveCpn");
  let now = ();
  while (() - now &lt; 100) {}
  return &lt;p&gt;Time-consuming components&lt;/p&gt;;
}
export default function App() {
  const [num, updateNum] = useState("");
  return (
    &lt;&gt;
      &lt;input
        type="text"
        onChange={(e) =&gt; updateNum()}
        value={num}
      /&gt;
      &lt;ExpensiveCpn /&gt;
    &lt;/&gt;
  );
}

The input box above will refresh the component<ExpensiveCpn/>, we can control rendering without using useMemo and other APIs, which is actually to separate the change from the unchanging 👇🏻:

function ExpensiveCpn() {
  ("ExpensiveCpn");
  let now = ();
  while (() - now &lt; 100) {}
  return &lt;p&gt;Time-consuming components&lt;/p&gt;;
}
function Input() {
  const [num, updateNum] = useState("");
  return (
    &lt;input
      type="text"
      onChange={(e) =&gt; updateNum()}
      value={num}
    /&gt;
  );
}
export default function App() {
  return (
    &lt;&gt;
      &lt;Input /&gt;
      &lt;ExpensiveCpn /&gt;
    &lt;/&gt;
  );
}

The components rendered in this way will only be<Input/>The components inside will not affect the outside.

Summarize

The above methods can be understood from several aspects:

  • Reduce the number of times you re-released: memo, useMemo, useCallback, avoid using inline objects, and separation between change and unchange.
  • Reduce rendering nodes: fragments and components are lazy to load.
  • Reduce rendering calculations: traversal attempts to use key.

This is the end of this article about the detailed explanation of the implementation method of React performance optimization. For more related React performance optimization content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!