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:

App.jsxconst [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:

App.jsxreturn (
  <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:

App.jsxconst [items, setItems] = useState([]);

A następnie napiszmy funkcję addItem do ich dodawania:

App.jsxfunction 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:

Items.jsxfunction 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:

App.jsxreturn (
  <>
    <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:

Items.jsxexport default memo(Items);

Nie zapomnijmy zaimportować memo:

Items.jsximport { 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:

App.jsximport { 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:

App.jsxconst 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ę.

Задание 1

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.

Задание 2

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.

Задание 3

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.

Задание 4

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.