Skocz do treści

useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami

108

Powszechnym zmartwieniem osób poznających komponenty funkcyjne jest tworzenie funkcji-callbacków przekazywanych dalej jako props. Wszak przy każdym renderze funkcja tworzona jest na nowo! Czy to aby nie marnotrastwo? Czy nie powoduje to problemów? React Hooks useMemo i useCallback przychodzą na ratunek!

Ten artykuł jest częścią 39 z 41 w serii React.js.

Zdjęcie Michał Miszczyszyn
JavaScript2 komentarze

Problem z funkcjami

Weźmy prosty komponent, w którym tworzony jest callback handleClick przekazywany jako props dalej:

function MyComponent({ oneProp, anotherProp }) {
  function handleClick() {
    console.log('Clicked!', oneProp, anotherProp);
  }

  return <SpecialButton onClick={handleClick} />;
}

Gdy ja pierwszy raz zobaczyłem taki kod, miałem mnóstwo wątpliwości — Ale po co? Dlaczego? Jak to? Przecież to nie ma sensu! Chętnie odpowiem Ci na te pytania osobiście: zapisz się na szkolenie z React.

Przede wszystkim, tworzenie nowej funkcji handleClick za każdym razem, gdy renderowany jest komponent wydawało mi się barbarzyństwem. Czy to aby nie ma tragicznego wpływu na wydajność?

No i dodatkowo, tworzymy nową funkcję, więc jako props do komponentu SpecialButton przekazywana jest zawsze nowa referencja. Oznacza to, że zostanie on przerenderowany za każdym razem.

Problemy z obiektami

Analogiczna sytuacja występuje, o czym wiele osób zdaje się nie myśleć, gdy w renderze tworzymy nowy obiekt:

function MyComponent({ oneProp, anotherProp }) {
  const options = {
    data: oneProp,
    data2: anotherProp,
    // …
  };

  return <SpecialComponent options={options} />;
}

Za każdym razem options tworzone jest na nowo, i jest nową referencją. Po przekazaniu tego obiektu jako props do SpecialComponent, ten przerenderuje się za każdym razem.

useCallback

Jest taki React Hook, który rozwiązuje pierwszy problem! Nazywa się useCallback. Jako pierwszy argument przyjmuje funkcję, a jako drugi taką samą tablicę zależności, jak useEffect.

function MyComponent({ oneProp, anotherProp }) {
  const handleClick = React.useCallback(() => {
    console.log('Clicked!', oneProp, anotherProp);
  }, [oneProp, anotherProp]);

  return <SpecialButton onClick={handleClick} />;
}

W ten sposób, funkcja przekazana do React.useCallback jest memoizowana. To znaczy, że dopóki nie zmienią się oneProp lub anotherProp, dopóty handleClick nie będzie nową funkcją.

React.useCallback zapamiętuje stworzoną funkcję i dzięki temu komponent SpecialButton nie przerenderuje się bez potrzeby!

useMemo

To React Hook, który zwraca zapamiętaną wartość. W pewnym sensie, to uogólniona wersja useCallback. Jako argument przyjmuje funkcję, która zwraca wartość.

function MyComponent({ oneProp, anotherProp }) {
  const options = useMemo(
    () => ({
      data: oneProp,
      data2: anotherProp,
      // …
    }),
    [oneProp, anotherProp],
  );

  return <SpecialComponent options={options} />;
}

W ten sposób obiekt options będzie za każdym razem tą samą referencją i komponent SpecialComponent nie będzie musiał się przerenderowywać — przynajmniej dopóki nie zmieni się oneProp lub anotherProp!

Czy Hooki useCallback i useMemo nie są zbędne?

Napisałem, że React Hook useMemo to trochę uogólniona wersja useCallback — i to prawda. useCallback jest funkcjonalnie równoważny takiemu zapisowi:

const myUseCallback = (fn, deps) => useMemo(() => fn, deps);

Kiedy używać

Z rozsądkiem ;) Nie powiem Ci dokładnie kiedy, ale ja bym używał zawsze, gdy może być z tego jakiś zysk, a nie ma negatywnego wpływu na czytelność.

Pytania?

Jeśli chcesz na bieżąco śledzić kolejne części kursu React.js to koniecznie polub mnie na Facebooku i zapisz się na newsletter.

👉  Znalazłeś/aś błąd?  👈
Edytuj ten wpis na GitHubie!

Autor