UseCallback and useMemo are sometimes confusing when writing React Hook code. Although we know that their function is to cache and optimize performance, we worry about negative optimization due to incorrect usage. This paper will elaborate on the common use of useCallback and useMemo in the development of the way and mistakes, and combined with the source code to analyze the reasons, and know why.

1.useCallback

1.1 Do not abuseuseCallback

Consider the following example:

import React from 'react';

function Comp() {
    const onClick = (a)= > {
        console.log('print');
    }
    
    return <div onClick={onClick}>Comp components</div>
}
Copy the code

We notice that onClick is reassigned when the Comp component triggers a refresh itself or as a child component that follows the parent. To “improve performance”, use useCallback to wrap onClick for caching purposes:

import React, { useCallback } from 'react';

function Comp() {
    const onClick = useCallback((a)= > {
        console.log('print'); } []);return <div onClick={onClick}>Comp components</div>
}
Copy the code

So the question is, is there any performance improvement? The answer is no, it’s worse than it used to be; When we rewrite the logical structure of the code, the reason becomes clear:

import React, { useCallback } from 'react';

function Comp() {
    const onClick = (a)= > {
        console.log('print');
    };
    
    const memoOnClick = useCallback(onClick, []);
    
    return <div onClick={memoOnClick}>Comp components</div>
}
Copy the code

Every line of extra code execution costs, even if it costs only a tiny bit of CPU heat. The official documentation states that there is no need to worry about creating functions that will cause performance problems, so using useCallback to modify the components in this scenario does not gain any benefit (the functions will still be created), but the cost of modifying the components (depending on whether or not the dependency changes). The more useCallback is used, the more the weight will be. From a javascript perspective, methods not wrapped in useCallback are garbage collected and redefined when a component is refreshed, but closures made by useCallback retain references to callback functions and dependencies.

1.2 useCallbackThe correct way to use

This is because useCallback is not designed to solve the problem of multiple function creation inside a component, but rather to reduce unnecessary repeated rendering of child components. In fact, there are two main optimization ideas in the React system:

  • 1. Reduce the number of times you rerender. React doesn’t trigger reconciliation unless render is present.
  • 2. Reduce the amount of computation, needless to say.

So consider the following scenario:

import React, { useState } from 'react';

function Comp() {
    const [dataA, setDataA] = useState(0);
    const [dataB, setDataB] = useState(0);

    const onClickA = (a)= > {
        setDataA(o= > o + 1);
    };
    
    const onClickB = (a)= > {
        setDataB(o= > o + 1);
    }
    
    return <div>
        <Cheap onClick={onClickA}>Cheap: {dataA}</div>
        <Expensive onClick={onClickB}>Component Expensive: {dataB}</Expensive>
    </div>
}
Copy the code

Expensive is a very Expensive component to render, but clicking on the Cheap component also causes Expensive to re-render, even if the dataB hasn’t changed. The reason is that onClickB has been redefined, causing React to determine that the component has changed when diff old and new components. This is where useCabllback and Memo come into play:

import React, { useState, memo, useCallback } from 'react';

function Expensive({ onClick, name }) {
  console.log('Expensive rendering');
  return <div onClick={onClick}>{name}</div>
}

const MemoExpensive = memo(Expensive);

function Cheap({ onClick, name }) {
  console.log('being rendered');
  return <div onClick={onClick}>{name}</div>
}

export default function Comp() {
    const [dataA, setDataA] = useState(0);
    const [dataB, setDataB] = useState(0);

    const onClickA = (a)= > {
        setDataA(o= > o + 1);
    };
    
    const onClickB = useCallback((a)= > {
        setDataB(o= > o + 1); } []);return<div> <Cheap onClick={onClickA} name={' Cheap: ${dataA} '}/> <MemoExpensive onClick={onClickB} name={' ${dataB}`} /> </div> }Copy the code

Memo is a new method added in React V16.6.0. It is similar to PureComponent, which optimizes Function Components and Class components. They both shallow compare the old and new data of the incoming component and do not trigger rendering if they are the same.

So useCallback ensures that onClickB doesn’t change, and clicking on the Expensive component doesn’t trigger the refresh of the Expensive component, only clicking on the Expensive component does. UseCallback and Memo are a great pair of tools for optimizing to reduce unnecessary rendering. Run the sample code

1.3 extensions

UseCallback source code:

// Initialization phase
function mountCallback(callback, deps) {
    const hook = mountWorkInProgressHook();
    const nextDeps = deps === undefined ? null : deps;
    hook.memoizedState = [callback, nextDeps];
    return callback;
}

// Update phase
function updateCallback(callback, deps) {
    consthook = updateWorkInProgressHook(); .const nextDeps = deps === undefined ? null : deps;
    const prevState = hook.memoizedState;
    if(prevState ! = =null) {
        if(nextDeps ! = =null) {
            const prevDeps = prevState[1];
            // Compare for equality
            if (areHookInputsEqual(nextDeps, prevDeps)) {
                // If equal, the old callback is returned
                return prevState[0];
            }
        }
    }
  
    hook.memoizedState = [callback, nextDeps];
    return callback;
}
Copy the code

The core logic is to compare whether the DEPS has changed and return the new callback function if it has changed, otherwise return the original function. The areHookInputsEqual comparison method calls the React is method:

// Exclude the following two special cases:
// +0 === -0 // true
// NaN === NaN // false

function is(x: any, y: any) {
  return( (x === y && (x ! = =0 || 1 / x === 1/ y)) || (x ! == x && y ! == y); ) ; }Copy the code

2.useMemo

2.1 Do not abuseuseMemo

import React, { useMemo } from 'react';

function Comp() {
    const v = 0;
    const memoV = useMemo((a)= > v, []);
    
    return <div>{memoV}</div>;
}
Copy the code

The overhead of creating memoV is unnecessary for the same reasons mentioned in the first section. UseMemo is necessary only when the creation itself is expensive (such as generating variable values after thousands of calculations), which is rare.

2.2 useMemoThe correct way to use

As we mentioned earlier, one of the two main ways to optimize React component performance is to reduce the amount of computation, which is where useMemo comes in:

import React, { useMemo } from 'react';

function Comp({ a, b }) {
    const v = 0;
    const calculate = (a, b) = > {
        / /... complex calculation
        return c;
    }
    
    const memoV = useMemo((a, b) = > v, [a, b]);
    
    return <div>{memoV}</div>;
}
Copy the code

3. Summary

React Hook requires a high level of team consistency. UseCallback and useMemo are good examples. More complex scenarios include the use of useRef and custom hooks. As a rule of thumb, teams need to enforce code review when doing Hook coding, otherwise hard-to-locate bugs or performance issues can easily occur. The current Hook methods are not perfect, and there is a lot of debate on Twitter. We look forward to a more mature and easy-to-use solution in the future version of React.