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()
.
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:
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.
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.
Krok 3
Ponownie klikamy przycisk „Rerender”. Podobnie jak w punkcie drugim, korzystamy z zapamiętanej funkcji.
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ę.
Krok 5
Następnie klikamy przycisk „Render”. Wartość „counter” pozostaje niezmieniona, zwracana jest kopia.
Krok 6
Klikamy przycisk „Counter”, zwracana jest nowa funkcja, ponieważ wartość counter (2 parametr useCallback) uległa zmianie.
Krok 7
Klikamy przycisk „Counter”, zwracana jest nowa funkcja, z tego samego powodu, co w kroku 6.
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.