This is the 10th day of my participation in the More text Challenge. For more details, see more text Challenge

preface

Hooks are new in React 16.8. It lets you use state and other React features without having to write a class

Hooks and functional components make it easier to develop React applications. This article introduces several methods for performance optimization of functional components.

Reduce render times

Use the React. Memo

React V16.6.0 provides the React.memo() API to solve the problem of the same props and still render components.

If your component renders the same result with the same props, you can improve the performance of the component by wrapping it in react.memo and memorizing the result of the component rendering. This means that in this case React will skip the render component and directly reuse the results of the most recent render.

The following is an example (from the official document) :

const MyComponent = React.memo(function MyComponent(props) {
  /* Render with props */
});
Copy the code

Note: React. Memo

  1. React.memoCheck only props changes. If the functional component has hooks for useState, useReducer, and useReducer inside, it will still rerender when the context changes.
  2. React.memoThis is a shallow comparison of props for complex objects. If you want to customize the comparison, you need to pass the comparison function as the second argument.

The following is an example (from the official document) :

function MyComponent(props) {
  /* Render with props */
}
function areEqual(prevProps, nextProps) {
  Return true if passing nextProps to render returns the same result as passing prevProps to render, false otherwise */
}
export default React.memo(MyComponent, areEqual);
Copy the code

Props passed to child components using the useCallBack, useMemo cache

The following code exists:

/ / the parent component
export default function App() {
  const [appCount, setAppCount] = useState(0);
  const [childCount, setChildCount] = useState(0);

  const handleAppAdd = () = > {
    setAppCount(appCount + 1);
  };

  const handleChildAdd = () = > {
    setChildCount(childCount + 1);
  };

  console.log('app render');

  return (
    <div>
      <h2>App</h2>
      <button onClick={handleAppAdd}>appCount + 1</button>
      <p>appCount: {appCount}</p>

      <h2>child</h2>
      <button onClick={handleChildAdd}>childCount + 1</button>
      <Child value={childCount} />
    </div>
  );
}

/ / the child component
import React from 'react';
const Child = props= > {
  console.log('child render');
  const { value } = props;
  return <div>Child: {value}</div>;
};
export default React.memo(Child);

Copy the code

The above Child component has been optimized using React. Memo so that it will not be rerendered if only the parent appCount changes.

Now we are modifying the Child component:

import React from 'react';
const Child = props= > {
  console.log('child render');
  const { value, handleAdd } = props;
  return <div>
	  <p>Child: {value}</p>
    <button onClick={handleAdd}>btn in child: childCount + 1</button>
  </div>;
};
export default React.memo(Child);
Copy the code

In the above code, we also added a button to the child component that can increment or subtraction ChildCount. When clicked, it calls the props. HandleAdd passed in the parent component.

// app.jsx
export default function App() {
  const [appCount, setAppCount] = useState(0);
  const [childCount, setChildCount] = useState(0);

  const handleAppAdd = () = > {
    setAppCount(appCount + 1);
  };

  const handleChildAdd = () = > {
    setChildCount(childCount + 1);
  };

  console.log('app render');

  return (
    <div>
      <h2>App</h2>
      <button onClick={handleAppAdd}>appCount + 1</button>
      <p>appCount: {appCount}</p>

      <h2>child</h2>
      <button onClick={handleChildAdd}>childCount + 1</button>
      <Child value={childCount} handleAdd={handleChildAdd} />
    </div>
  );
}
Copy the code

The above code passes the Child the props of the handleAdd. When we click appCount + 1 again, childCount does not change, but the Child is refreshed. The log looks like this:

From this we can see that it was the new props. HandleAdd passed to the Child that caused the re-render. Because handleAdd is regenerated as a new function each time in the App component, react. memo changes the props and rerender the Child.

So how do we solve the problem of functions on props? React. UseCallback can solve this problem.

Passing the inline callback function and the array of dependencies as arguments to useCallback returns a memoized version of the callback function that is updated only if one of the dependencies changes.

UseCallback (fn, deps) equals useMemo(() => fn, deps).

As you can see from the official documentation, useCallback returns the same function as long as deps are unchanged. Similarly, if props are complex objects, we can use useMemo to handle them.

Child repeats render’s problem, we deal with it as follows:

// App.js

const handleChildAdd = useCallback(() = > {
  setChildCount(childCount + 1);
}, [childCount]);

Copy the code

Click here for sample code

Render optimization for placeholder components

We usually have a requirement that a child component has a placeholder area that needs to be passed in via props. We can reduce the rendering of the placeholder component by creating a react.element in the parent component.

The code is as follows:

const Content = () = > {
  console.log('Content render');
  return <div>content</div>;
};


/ / sample 1
export default function App() {
  const Content = () = > {
    console.log('Content render');
    return <div>content</div>;
  };
  return <Child content={Content} />;
}
const Child = (props) = > {
  const Content = props.content
  return (
    <>
      <header>The head</header>
      <main>
        <Content />
      </main>
    </>)}/ / sample 2
export default function App() {
  return <Child content={Content} />;
}
const Child = (props) = > {
  const Content = props.content
  return (
    <>
      <header>The head</header>
      <main>
        <Content />
      </main>
    </>)}/ / sample 3
export default function App() {
  return <Child content={<Content />} / >;
}

const Child = (props) = > {
  const Content = props.content
  return (
    <>
      <header>The head</header>
      <main>
        {props.content}
      </main>
    </>)}Copy the code

In the above code, we need to pay attention to the way the Child component is passed into the content.

  1. Example 1: It is passed every timerenderIt’s all regeneratedThe Content components
  2. Example 2: The Content component is passed in
  3. Example 3: What is passed is<Content />In the form ofReact.Element

The performance comparison is as follows

  1. Example 1andExample 2It’s all coming inContentComponents, they are inChildEvery timerenderNeeds to be rerenderedContentcomponent
  2. Example 1In the parent componentrenderWill generate a newThe Content components, which leads to every timerenderIn the process ofThe Content componentsThe generatedReact.ElementThe correspondingtypeWill be different, soThe Content componentsIt’s constantly destroying and creating.
  3. Example 3Relatively speakingThe optimal solution. The relativeExample 1The parent componentrenderDoes not destroy the creation whenContent. The relativeExample 2.ChildcomponentrenderContentComponents do not reworkrender.

React.Context read and write separation

The read and write analysis section is fromThis article

Before separation:

const LogContext = React.createContext();

function LogProvider({ children }) {
  const [logs, setLogs] = useState([]);
  const addLog = (log) = > setLogs((prevLogs) = > [...prevLogs, log]);
  return (
    <LogContext.Provider value={{ logs.addLog}} >
      {children}
    </LogContext.Provider>); } In the morning dream//juejin.cn/post/6889247428797530126Source: gold mining copyright belongs to the author. Commercial reprint please contact the author to obtain authorization, non-commercial reprint please indicate the source.Copy the code

When the Provider’s value changes, all the consuming components within it are rerendered. Providers and their internal Consumer components are not subject to the shouldComponentUpdate function, so consumer components can update if their ancestors quit updating.

In the pre-optimized code, value is passed in as soon as setLogs update the state. All the functions that use logContext will be updated.

The optimized code:

function LogProvider({ children }) {
  const [logs, setLogs] = useState([]);
  const addLog = useCallback((log) = > {
    setLogs((prevLogs) = >[...prevLogs, log]); } []);return (
    <LogDispatcherContext.Provider value={addLog}>
      <LogStateContext.Provider value={logs}>
        {children}
      </LogStateContext.Provider>
    </LogDispatcherContext.Provider>); } In the morning dream//juejin.cn/post/6889247428797530126Source: gold mining copyright belongs to the author. Commercial reprint please contact the author to obtain authorization, non-commercial reprint please indicate the source.Copy the code

Above we use LogDispatcherContext and LogStateContext to separate read and write. Components that only use addLog are not refreshed as logs change.

Lazy initial value

For lazy initial values, refer to the hook-FAQ section of the React official documentation

Create the initial state lazily

Normal use of useState:

const [state, setState] = useState(initialState);
Copy the code

The initialState parameter is only used in the initial rendering of the component and is ignored in subsequent renderings. If the initial state needs to be obtained through complex calculations, you can pass in a function that evaluates and returns the initial state, which is called only at the time of the initial rendering.

Lazy initial state document

Lazily creates the initial value of useRef()

UseRef does not accept a special function overload as useState does. Instead, you can write your own functions to create and set them to lazy

function Image(props) {
  const ref = useRef(null);

  // ✅ IntersectionObserver is created lazily only once
  function getObserver() {
    if (ref.current === null) {
      ref.current = new IntersectionObserver(onIntersect);
    }
    return ref.current;
  }

  // Call getObserver() when you need it
  // ...
}
Copy the code

The resources

  • React official documents
  • hooks-faq
  • What did I learn at work writing React? Performance Optimization