React Hooks: useState — wiele stanów, callbacki i inne niuanse

Ten wpis jest 35 częścią z 39 w kursie React.js
wp-content/uploads/2017/10/React_logo_wordmark-300x101-1-e1508612391308.png
  1. React.js: Wprowadzenie do kursu 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: globalny store i jednokierunkowy przepływ danych
  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
  37. React Hooks a żądania do API
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami

W poprzednim wpisie omówiłem wstępnie React Hook useState. Pod postem na blogu oraz na Facebooku pojawiło się wiele komentarzy z pytaniami. Chciałbym odpowiedzieć na nie i wyjaśnić kilka niuansów tutaj. Jak to jest z rozbudowanym stanem? Czy można wywoływać useState w jednym komponencie wiele razy? I jak budować stan w oparciu o istniejący? To wszystko w tym wpisie.

setState przyjmuje funkcję lub stan

W poprzednim przykładzie pokazywałem tylko najprostsze wywołanie useState i funkcji zmieniającej stan:

function App() {
  const [counter, setCounter] = React.useState(0);

  return (
    <div>
      {counter}
      <button onClick={() => setCounter(counter + 1)}>+</button>
    </div>
  );
};

Sporo kodu, ale skup się na tym fragmencie: setCounter(counter + 1). Ustawiam tutaj nowy stan używając poprzedniego stanu. Mogę też jednak zrobić to nieco inaczej i przekazać do setCounter funkcję:

setCounter(counter => counter + 1);

W ten sposób sprawiłem, że sama zmiana stanu stała się całkowicie niezależna od stałych (i zmiennych) znajdujących się w danym zasięgu. Dodatkowo daje to tę przewagę, że taką funkcję można łatwiej przenieść poza komponent!

useState nadpisuje stan

W odróżnieniu od this.setState(…) w klasach, useState nadpisuje cały stan podaną wartością. Dla przypomnienia: this.setState(…) łączy podaną wartość z istniejącym stanem. Jest to zupełnie inne zachowanie i trzeba na nie zwrócić uwagę:

// wewnątrz klasy
state = { a: 1 };

// …

this.setState({ b: 2 });
// `this.state` teraz to { a: 1, b : 2 }

Porównaj to z zachowaniem useState:

function MyComponent() {
  const [state, setState] = React.useState({ a: 1 });

  // …
  setState({ b: 2 });
  // `state` teraz to { b: 2 }
}

Można by się pokusić o użycie formy funkcyjnej setState i ręczne połączenie starych wartości z nowymi:

setState(state => ({ ...state, b: 2 }));
// `state` to teraz { a: 1, b: 2 }

Ale nie zalecam takiego rozwiązania. Zamiast tego…

useState można wywołać wiele razy

W klasycznym podejściu, w klasach, this.state przechowywało cały stan danego komponentu. W przypadku Hooków można to zrobić inaczej! useState możesz wywołać wiele razy:

function MyComponent() {
  const [counter, setCounter] = React.useState(0);
  const [position, setPosition] = React.useState('top');

  // …
}

W ten sposób w jednym „stanie” trzymam tylko rzeczy dotyczące jednego konceptu. Nie muszę mieszać wszystkiego ze sobą w wielkim this.state. Ponadto, ogromną zaletą tego rozwiązania jest to, że takie kawałki stanu mogę sobie łatwo wydzielić do własnych Hooków. Jak to zrobić? O tym w jednym z kolejnych wpisów.

Alternatywą dla przechowywania rozbudowanego stanu jest też Hook useReducer. O nim również nieco później.

Stan początkowy może być funkcją

React.useState(…) jako argument przyjmuje stan początkowy. Możesz też podać tam funkcję, aby uzyskać specjalne zachowanie. Taka funkcja będzie wywołana tylko raz, przy zamontowaniu komponentu, a jako stan początkowy zostanie ustawiona zwrócona przez nią wartość.

function MyComponent(props) {
  const [state, setState] = React.useState(() => {
    return calculations(props);
  });
}

W jakim przypadku ma sens tak skomplikowany zapis? Wtedy, gdy funkcja calculations jest skomplikowana, a jej wywołanie może zająć chwilę. W takim wypadku nie ma sensu wywoływać jej przy każdym renderze, a zamiast tego możesz użyć dodatkowej funkcji przekazanej do Hooka useState(…). W praktyce — pewnie Ci się to zbyt często nie przyda.

Pytania?

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

Podsumowanie

Omówiłem tutaj kilka niuansów związanych z Hookiem useState w Reakcie. Możesz mieć teraz wrażenie, że to strasznie skomplikowana sprawa, skoro stworzyłem o tym aż dwa wpisy. tak naprawdę jednak Hooki to znaczne uproszczenie dla istniejących obecnie API i na pewno przyjemnie będzie Ci się z nich korzystało.

Nawigacja po kursie:
  1. React.js: Wprowadzenie do kursu 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: globalny store i jednokierunkowy przepływ danych
  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
  37. React Hooks a żądania do API
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami