The introduction

Since react Hooks came into being, more and more people and teams have been using Them. Many people think that useCallback is a great way to solve performance problems, but are you using it right?

The following is the author’s practical conclusion on how to use useCallback to improve performance in specific scenarios.

Background knowledge

When we talk about how useCallback can solve performance problems, we’ll talk about re-render. It’s a known fact that a parent’s re-render triggers a child’s re-render in react, but sometimes re-render is unnecessary. For example, if the parent component does not pass props to the child component, the render result does not change.

When you run the following example, you can see that the setState is triggered by the input, which triggers the re-render of the Case1 component. When the parent component re-render, the child component A will also re-render. So every time you type input, you’re going to see in the console that there’s a log for render_A.

// case1 class extends React.Com {rerender render() {console.log("render_A"); Return <div> This is A component </div>; } } export default function Case1() { const [count, setCount] = useState(0); const onChange = (data) => { setCount(data.target.value); }; return ( <> <input value={count} onChange={onChange} /> <A /> </> ); }Copy the code

useCallback

How to use useCallback to solve the sub-component re-render problem

The example above illustrates the waste of new energy, so how to use useCallback to solve the problem of subcomponent re-render?

Here’s what the official useCallback document explains:

Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).

UseCallback takes two arguments, the first an inline callback function and the second an array of dependencies. Using useCallback will return a memoized version of the callback function when a dependency changes. When you pass the callback function to a passing sub-component, using useCallback will avoid unnecessary rendering because of the equivalence of props.

Is it really the right way to use it?

Wrong use Case 1

In the following example, subcomponent A’s callback function already uses useCallback, but when you change the count value via input, subcomponent A keeps re-render.

// case2 const A = ({onClick}) => {// rerender console.log("case2: render_A"); Return <button onClick={onClick}>A component +count</button>; }; export default function Case2() { const [count, setCount] = useState(0); const onClick = useCallback(() => { setCount((count) => count + 1); } []); return ( <> <p>count:{count}</p> <A onClick={onClick} /> </> ); }Copy the code

Why didn’t the above case avoid invalid re-render?

Because functional components avoid re-render and need to be used in conjunction with React.memo. The function component is packaged with the higher-order component React.memo. It is similar to the PureComponent of the class component in that it gives props a shallow comparison (based on the memory address) to determine whether to update it.

In function components, functions that are passed as props to child components, whether they are pureComponent or packaged with React.memo, will render the child component. Using useCallback will render the child component without the parent component.

If the above case is modified, the following will not re-render

Const A = ({onClick}) => {// rerender console.log("case2: render_A"); Return <button onClick={onClick}>A component +count</button>; }; const B = React.memo(A); export default function Case2() { const [count, setCount] = useState(0); const onClick = useCallback(() => { setCount((count) => count + 1); } []); return ( <> <p>count:{count}</p> <B onClick={onClick} /> </> ); }Copy the code

Use useCallback with dependencies

Use useCallback for dependencies.

Wrong use Case # 2

In the example below, we use useCallback in the callback function of subcomponent B, but do not add any dependencies. So count in the onClick useCallback callback is always 0. After changing the value in the input, click on the A component, and the effect will always be 1 in the console.

Const A = ({onClick}) => {// rerender console.log("case2: render_A"); Return <button onClick={onClick}>A component +count</button>; }; const B = React.memo(A); export default function Case2() { const [count, setCount] = useState(0); const onClick = useCallback(() => { console.log(count + 1); // Here count is always 0}, []); return ( <> <p>count:{count}</p> <B onClick={onClick} /> </> ); }Copy the code

Add count to useCallback as dependencies. After changing the value in input, click on A component to print the latest value of count in console. Therefore, when using useCallback, make sure to sort out the dependencies of the current callback function to avoid bugs caused by the value not updated. For example, when retrieving data in pages, it always obtains the data on the first page.

【 Example only to illustrate the importance of dependencies correctly 】

Const A = ({onClick}) => {// rerender console.log("case2: render_A"); Return <button onClick={onClick}>A component +count</button>; }; const B = React.memo(A); export default function Case2() { const [count, setCount] = useState(0); const onChange = (data) => { setCount(data.target.value); }; const onClick = useCallback(() => { console.log(count + 1); }, [count]); return ( <> <p>count:{count}</p> <input value={count} onChange={onChange} /> <B onClick={onClick} /> </> ); }Copy the code

After adding dependencies, when dependencies change, the child component re-render along with the parent component.

So, in the concrete use of re – if the parent component factors render again at the same time are all subcomponents useCallback dependencies, would not have used useCallback redundant, anyway, will render with the parent component. Just like in the case above.

How to read a frequently changing value from useCallback can be viewed in the official document:The English version.The Chinese version of

Use useCallback+React.memo to reduce the number of times the child must render if there are many factors that trigger the parent but few that trigger the child.

Note: When using Dependencies, use useEffect to refer to dependencies as a non-pure function. When using useCallback, avoid infinite loops. In practice, the most easy to appear a kind of infinite loop is to request the data of the page in the impure function, set it to State, and then use the impure function as the useEffect dependencies, then re-render after setState, The impure function caused by re-render is a new instance, which is called again as a dependency, thus getting stuck in an infinite loop.

useMemo

Use usememo instead of useCallback if it is a function with complex computations in its components. Because of the reference to the useCallback cache function, the useMemo cache calculates the value of the data. UseMemo is an optimized strategy to avoid costly calculations every time you render.

UseMemo needs to pass in two arguments. The first argument is callback, and the second argument is dependencies. The first argument is callback, and the second argument is dependencies. As useCallback/useEffect is an imported external parameter or a dependent parameter.

UseMemo returns a memoized value. In the case of constant dependencies, useMemo returns the last first calculated value. When dependencies change, useMemo automatically recalvaluates and returns a new memoized value.

Use cases are as follows:

const memoizedValue = useMemo(() => calculateFunc(a, b), [a, b]);
Copy the code

The value of memoizedValue does not change if the variable values of A and B do not change. That is, the first parameter function of the useMemo function will not be executed, so as to save computation.

At the end

That’s how the useCallback and useMemo are used and why they can be used to optimize performance.