Hook optymalizacji wydajności useCallback w React
W tej lekcji przyjrzymy się następującemu
hookowi do optymalizacji wydajności
useCallback.
Hook useCallback jest podobny do API useMemo,
różnica polega na tym, że pierwszy
przechowuje w pamięci podręcznej wartość między momentami przerysowania
ekranu, a drugi - funkcję zwrotną (callback).
Pozwala nam to nie uruchamiać ponownie wymagających zasobów
funkcji, gdy nie jest to wymagane i może
być używane przy
przekazywaniu funkcji
do komponentów potomnych.
Przyjrzyjmy się temu dokładniej na przykładzie.
Na początek utwórzmy komponent App
i utwórzmy w nim stan num:
const [num, setNum] = useState(0);
Niech będzie przycisk, po kliknięciu
którego num zwiększa się
o 1, oraz akapit, w którym
będziemy wyświetlać wartość num:
return (
<div>
<button onClick={() => setNum(num + 1)}>kliknij</button>
<p>kliknięcia: {num}</p>
</div>
);
A teraz załóżmy, że w
App wyświetla się jeszcze jakaś lista
z elementami, którą będziemy uzupełniać
po naciśnięciu innego przycisku. Do przechowywania
elementów tej listy utwórzmy
stan items:
const [items, setItems] = useState([]);
A następnie napiszmy funkcję addItem
do ich dodawania:
function addItem() {
setItems([...items, 'nowy element']);
}
Teraz napiszmy kod do wyświetlania
elementów listy i przenieśmy go do komponentu potomnego
Items, który w postaci
propsów będzie otrzymywać tablicę elementów
i funkcję do ich dodawania. Nie zapomnijmy
dodać wyjścia do konsoli, aby widzieć
kiedy nasz Items będzie
przerysowywany:
function Items({ items, addItem }) {
const result = items.map((item, index) => {
return <p key={index}>{item}</p>;
});
console.log('Renderowanie Items');
return (
<div>
<h3>Nasze elementy</h3>
{result}
<button onClick={addItem}>dodaj element</button>
</div>
);
}
export default Items;
Umieśćmy Items na końcu komponentu
App i przekazujmy mu tablicę
items i funkcję do dodawania
elementów addItem:
return (
<>
<div>
<button onClick={() => setNum(num + 1)}>kliknij</button>
<p>kliknięcia: {num}</p>
<br />
</div>
<Items items={items} addItem={addItem} />
</>
);
A teraz ponaciśnijmy na przyciski
i przekonajmy się, że num rośnie i
nowe elementy dodają się do listy.
A otwierając konsolę, zobaczymy, że
nasza lista przerysowuje się za każdym
razem, nawet jeśli klikamy na przycisk,
który zwiększa num.
Jeśli mamy małą listę, to wszystko
w porządku, a jeśli zakłada się, że będzie
obszerna i jest tam jeszcze wiele innych rzeczy?
Nie problem - powiesz, przecież na poprzedniej
lekcji poznaliśmy API memo,
żeby właśnie unikać niepotrzebnych przerysowań
komponentu.
Więc zawińmy nasz komponent
Items w memo i po sprawie.
Nawiasem mówiąc, można to zrobić bezpośrednio
przy eksporcie Items:
export default memo(Items);
Nie zapomnijmy zaimportować memo:
import { memo } from 'react';
A teraz otwórzmy konsolę i ponaciśnijmy
na przyciski. Wszystkie starania na marne! My
zmemowaliśmy komponent, ale po naciśnięciu
na przycisk 'kliknij' komponent
Items i tak
przerysowuje się za każdym razem.
Chodzi o to, że kiedy komponent rodzica
jest przerysowywany, jego funkcje
są ponownie tworzone - dotyczy to również naszej
funkcji addItem, którą przekazujemy do
Items.
Właśnie w tym momencie pomoże nam hook
useCallback. Zastosujmy
go. Na początek zaimportujmy go do
App:
import { useCallback } from 'react';
Następnie przeróbmy proste zadeklarowanie funkcji
addItem na
Wyrażenie funkcyjne, wskażmy jako
pierwszy parametr dla useCallback
naszą funkcję w postaci callbacka. Drugim
parametrem w nawiasach kwadratowych wskażmy
zależności - wszystkie reaktywne zmienne,
uczestniczące w funkcji, w naszym przypadku
to tablica items:
const addItem = useCallback(() => {
setItems(() => [...items, 'Nowy element']);
}, [items]);
Gotowe! W ten sposób zapisaliśmy w pamięci podręcznej
funkcję. Naciskamy ponownie na przyciski i
widzimy, że teraz po naciśnięciu na przycisk
'kliknij' nasz komponent potomny nie
przerysowuje się.
Utwórz komponent App, umieść
w nim akapit z tekstem. Utwórz
stan z wartością początkową 'tekst'
i wyświetl go w akapicie. Niech po kliknięciu
na akapit na końcu tekstu
dodaje się wykrzyknik.
Utwórz komponent potomny Products,
w którym będzie przycisk do dodawania
nowego produktu. Umieść go w App.
W komponencie rodzica utwórz stan
z tablicą produktów i funkcję dodawania
nowego produktu. Przekaż je
jako propsy do komponentu potomnego, wyświetl w nim
przekazaną tablicę w postaci listy ul.
W Products wyświetl w konsoli tekst
'renderowanie produktów'.
Zawiń Products w memo.
Klikaj na akapit i przycisk. Upewnij się,
że po kliknięciu na akapit komponent potomny
i tak się przerysowuje.
Zapisz w pamięci podręcznej funkcję do dodawania
produktów, zawijając ją w hook useCallback.
Klikaj na akapit i przycisk. Upewnij się,
że po kliknięciu na akapit, komponent potomny
już się nie przerysowuje.