useCallback jest hookiem, który wszedł do Reacta tak jak inne hooki od wersji 16.8. Hook ten zwraca zapamiętaną funkcję, a my mamy pełną kontrolę, kiedy zwrócimy nową funkcję, a kiedy zapamiętaną funkcję zwrotną, wpływając na poprawę optymalizacji.

Co mówi nam oficjalna dokumentacja Reacta?
(https://pl.reactjs.org/docs/hooks-reference.html#usecallback)

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

Zwraca zapamiętaną (ang memoized) funkcję zwrotną (ang. callback).

Ok, przejdźmy do przykładu.

Aby zasymulować działanie useCallback() stworzymy komponent główny. Nazwiemy go <Example/>. Utworzymy również drugi komponent o nazwie <ChildComponent/>, który będzie przyjmował zapamiętaną, bądź nową funkcję. To zapewni nam useCallback().

Wewnątrz utworzymy licznik poprzez użycie hooka useState o nazwie "counter" . Będzie on nam potrzebny do zasymulowania działania hooka useCallback i pokazania, jak działa drugi parametr useCallback. Od drugiego parametru bowiem zależy to, czy zwracana jest zapamiętana funkcja, czy nowa funkcja.

Użyjemy również hooka useState aby zmieniać stan komponentu tylko po to, aby ponownie renderować komponent. Ponowne przerenderowanie będzie miało na celu sprawdzenie, w jaki sposób zwracana jest funkcja poprzez useCallback. Posłuży nam do tego state stworzymy stan o nazwie "rerender".

W końcu stworzymy funkcję używając hooka useCallback().

Poglądowy podział komponentów, state, funkcji i przepływu

Na początku może być to niejasne, natomiast zachęcam do prześledzenia kodu i opisu krok po kroku z wynikiem z Profilera.

Całość kodu wygląda tak:

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

// Komponent podrzedny, przyjmujacy funkcje
const ChildComponent = ({exampleFunction}) => (
    <button onClick={exampleFunction}>Click</button>
);

// Komponent glowny - sterujacy cala "aplikacja" ;)
function Example() {
    // sluzy do przerenderowania komponentu Example i komponentu podrzednego
    const [rerender, setRerender] = useState(false);
    // drugi parametr useCallback, odpowiadajacy za zwrocenie nowej funkcji 
    // (na podstawie counter)
    const [counter, setCounter] = useState(0);
    
    const exampleFunction = useCallback(
        () => {
            alert('clicked ' + Date.now());
        },
        [counter]
    );

    return (
        <div>
            <button onClick={() => setRerender(!rerender)}>Rerender</button>
            <br />
            <button onClick={() => setCounter(counter + 1)}>Counter</button>
            <br />
            <ChildComponent exampleFunction={exampleFunction} />
            <br />
            Counter: {counter.toString()}
            <br />
            Rerender: {rerender.toString()}
        </div>
    );
}

export default Example;

Zaś od strony widoku tak:

React hooks - useCallback

Prześledźmy teraz działanie programu krok po kroku, aby lepiej zrozumieć useCallback(). Zobaczmy co mówi nam Profiler na temat komponentu…

Krok 1
Na początek następuje pierwsze renderowanie, zainicjowane są wartości w useState oraz tworzona jest zapamiętana funkcja dzięki useCallback, która dopiero po zmianie wartości w counter utworzy się na nowo.

React hooks - useCallback - krok 1

Krok 2
Klikamy przycisk „Rerender”. Komponent przerenderuje się, wraz z nim również komponent . W związku z tym, że wartość counter nie zmieniła się, zwracana jest zapamiętana funkcja.

React hooks - useCallback - krok 2

Krok 3
Ponownie klikamy przycisk „Rerender”. Podobnie jak w punkcie drugim, korzystamy z zapamiętanej funkcji.

React hooks - useCallback - krok 3

Krok 4
Teraz klikany na przycisk „Counter”. W związku z tym, że zmieniła się wartość „Counter”, komponent przerenderowuje się, a useCallback() zwraca nam nową funkcję.

React hooks - useCallback - krok 4

Krok 5
Następnie klikamy przycisk „Render”. Wartość „counter” pozostaje niezmieniona, zwracana jest kopia.

React hooks - useCallback - krok 5

Krok 6
Klikamy przycisk „Counter”, zwracana jest nowa funkcja, ponieważ wartość counter (2 parametr useCallback) uległa zmianie.

React hooks - useCallback - krok 6

Krok 7
Klikamy przycisk „Counter”, zwracana jest nowa funkcja, z tego samego powodu, co w kroku 6.

React hooks - useCallback - krok 7

Mam nadzieję, że powyższy przykład wraz z opisem, co dzieje się w każdym kroku pozwoli Wam bardziej zrozumieć hooka useCallback. Pamiętajcie o zdefiniowaniu odpowiednich wartości w tablicy (2 parametr).

Zastosowanie

W mojej opinii nie ma jedynej prawdziwej odpowiedzi gdzie i kiedy używać useCallback, bo jak zwykle – to zależy.
Natomiast, jeżeli jesteśmy w stanie przekazać typy proste do useCallback (drugi parametr), a wewnątrz funkcji obliczenia są dosyć złożone i w dodatku wartości nie zmieniają się zbyt często, warto zastanowić się, czy użycie useCallback nie będzie dobrym rozwiązaniem, które zapewni nam optymalizację.

React Developer Tools – Profiler

Aby móc prześledzić w analogiczny sposób zachowania komponentów należy zainstalować dodatek React Developer Tools (dostępny np. do Chrome).
Następnie klikamy F12 w celu otworzenia Chrome Dev Tools.
Przechodzimy do sekcji Profiler i klikamy ikonę odświeżenia.
Po skończonym badaniu ponownie klikamy ikonkę, po czym na naszym ekranie ukaże się rezultat profilowania.

Zadanie domowe

Zachęcam do skopiowania kodu z artykułu zabawy z hookiem useCallback.
Zmień również useCallback na zwykłą funkcję (bez użycia hooka) i sprawdź w jaki sposób zachowają się nasze komponenty po przejściu analogicznej ścieżki z artykułu. Jest to również doskonała okazja do zabawy Profilerem.

Źródła:
Diagram przygotowany przy użyciu narzędzia draw.io.