React Hooks: useEffect — efekty uboczne w komponencie

Ten wpis jest 36 częścią z 36 w kursie React.js
wp-content/uploads/2017/10/React_logo_wordmark-300x101-1-e1508612391308.png
  1. Wprowadzenie do kursu React.js od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie

W tym wpisie opowiadam o hooku useEffect w React. Na pewno często musisz wykonywać żądania HTTP wewnątrz komponentów, prawda? Albo nasłuchiwać jakichś subskrypcji? Jak często zdarzyło Ci się wykonywać dokładnie ten sam kodcomponentDidMount, a potem też w componentDidUpdate? Mi cały czas się to przytrafia! A do tego jeszcze pamiętać o posprzątaniu po sobiecomponentWillUnmount… Wciąż o tym zapominam. Ale już niedługo: Powitaj useEffect!

useEffect

W React 16.8 pojawiły się hooki, które pozwalają m.in. na tworzenie stanowych komponentów funkcyjnych, a także na wykonywanie efektów ubocznych w funkcjach. Dokładnie temu służy hook useEffect.

Jakie to mogą być efekty uboczne? Wszystko, co dzieje się asynchronicznie lub poza komponentem:

  • zapytania do API (fetch)
  • subskrypcje (np. rxjs albo EventEmitter)
  • timery (setTimeoutsetInterval)
  • aktualizacja document.title
  • nasłuchiwanie na zdarzenia — np. resize

Żadne z wymienionych rzeczy nie powinny znaleźć się bezpośrednio w komponencie. Zamiast tego, użyj useEffect!

Pierwszy useEffect

Skorzystamy tutaj z przykładu z poprzedniego wpisu:

React Hooks: useState, czyli stan w komponentach funkcyjnych

Chcesz, aby document.title był aktualizowany za każdym razem, gdy wpisujesz coś w input i filtrujesz listę użytkowników. Użyjesz wtedy dwóch hooków: useStateuseEffect:

function App() {
  const [filteredUsers, setUsers] = React.useState(allUsers);

  function filterUsers(e) {
    const text = e.currentTarget.value;
    const filteredUsers = getFilteredUsersForText(text);
    setUsers(filteredUsers);
  }

  useEffect(() => { // 1
    document.title = `Showing ${filteredUsers.length} users!`
  });

  return (
    <div>
      <input onInput={filterUsers} />
      <UsersList users={filteredUsers} />
    </div>
  );
};

Jedyne, co się tutaj zmieniło w stosunku do ostatniego wpisu, to 3 nowe linie:

  useEffect(() => { // 1
    document.title = `Showing ${filteredUsers.length} users!`
  });

Ten krótki kod sprawia, że przy każdym renderze wywoła się przekazana do useEffect funkcja i zaktualizowany zostanie document.title.

Przy każdym renderze?

Tak. Domyślnie, tak. W wielu przypadkach to pożądane zachowanie! Ale tutaj, możesz nieco zoptymalizować. Chciałabyś pewnie, aby nasz efekt wywoływał się wyłącznie wtedy, gdy zmienia się filteredUsers. To na tyle popularne zastosowanie, że jest wbudowane w useEffect. Nie trzeba korzystać z componentDidUpdate i manualnie porównywać zmienionych propsów. Zamiast tego…

  useEffect(() => {
    document.title = `Showing ${filteredUsers.length} users!`
  }, [filteredUsers]); // 2

Dodaję do useEffect drugi argument. Jest to tablica wartości, na podstawie których React podejmuje decyzję, czy dany efekt wywołać ponownie, czy nie.

Jeśli podajesz tablicę jako argument do useEffect, to koniecznie zwróć uwagę, aby zawierała ona listę wszystkich wartości z komponentu, które są używane w samym hooku.

Demo: React Hooks w przykładach: useEffect.

Sprzątanie po sobie

Bardzo częstym wymaganiem jest, dodatkowo, wykonanie jakiejś akcji (jakiegoś „efektu”), gdy trzeba po sobie posprzątać. Na przykład, gdy komponent jest odmontowywany, albo po prostu przerenderowywany. Ten przypadek również jest obsłużonyn przez useEffect! Wystarczy wewnątrz niego zwrócić funkcję:

  useEffect(() => {
    const subscription = props.status$.subscribe(handleStatusChange); // 3

    return () => { // 4
      subscription.unsubscribe();
    }
  });

W linii (3) podpinam się pod jakąś subskrypcję — w tym przypadku załóżmy, że jest to strumień rxjs. Moja funkcja „sprzątająca”, którą zwracam (4) usuwa subskrypcję, bo nie będzie już potrzebna.

Pamiętaj, aby zawsze usuwać wszelkie subskrypcje i timery, gdy nie są już potrzebne!

Zwróć uwagę, że subscribeunsubscribe wywoływane są teraz przy każdym renderze! To dobre zachowanie. Pomaga uniknąć subtelnych błędów, gdy zapominamy się ponownie podpiąć pod subskrypcję, gdy ta się zmienia. Dopóki nie sprawia to problemów — ja bym się tym szczególnie nie przejmował.

Są jednak sytuacje, gdy podpinanie subskrypcji na nowo przy każdym renderze jest niedopuszczalne i niepotrzebne. Kiedy? No na przykład wtedy, gdy z subskrypcją związane są jakieś kolejne efekty uboczne — np. skasowanie naszego timera (i przez to pomyłka w odliczaniu czasu), albo wysłanie kolejnego żądania do websocketów z prośbą o odpięcie i potem ponowne podpięcie do nasłuchiwania na zdarzenia.

W takiej sytuacji, jak już pisałem wcześniej, wystarczy jako drugi argument useEffect przekazać tablicę. W tym przypadku byłoby to:

  useEffect(() => {
    const subscription = props.status$.subscribe(handleStatusChange);

    return () => {
      subscription.unsubscribe();
    }
  }, [props.status$]); // 5

Mówisz tutaj Reaktowi: „usuń tę subskrypcję i stwórz ją na nowo tylko wtedy, gdy zmieni się props.status$”. To genialne ułatwienie i prosta optymalizacja!

A jakby tak klasą?

Porównaj teraz powyższy kod z analogicznym kodem w Reaktowej klasie:

  componentDidMount() {
    this.subscription = props.status$.subscribe(this.handleStatusChange);
  }

  componentDidUpdate(prevProps) {
    if (prevProps.status$ !== this.props.status$) {
      this.subscription.unsubscribe();
      this.subscription = props.status$.subscribe(this.handleStatusChange);
    }
  }

  componentWillUnmount() {
    this.subscription.unsubscribe();
  }

Ten kod robi dokładnie to samo, a jest dwukrotnie dłuższy!

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.

Nawigacja po kursie:
  1. Wprowadzenie do kursu React.js od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie