React oferuje dwa sposoby na utrzymanie danych wewnątrz komponentu: useState i useRef. Na pierwszy rzut oka wyglądają podobnie. Oba zwracają coś, co można odczytać, oba przetrwają ponowne renderowanie i oba pozwalają zapamiętać wartość między kolejnymi kliknięciami. Jeśli jednak wybierzesz niewłaściwy, skończysz albo z ekranem, który odmawia aktualizacji, albo z niekończącym się ciągiem niepotrzebnych renderowań. Wybór nie dotyczy składni. Dotyczy tego, czy React musi o czymś wiedzieć.

Co tak naprawdę robi każdy z tych hooków

useState to oficjalny kanał komunikacji Reacta z komponentem. Gdy go wywołasz, otrzymujesz wartość oraz funkcję ustawiającą (setter). React śledzi tę wartość jako część tożsamości komponentu. Gdy funkcja ustawiająca zostanie uruchomiona, React stwierdza: „Coś się zmieniło” i planuje nowe renderowanie, aby ekran mógł zostać zaktualizowany.

useRef z kolei to nic innego jak zwykły obiekt JavaScript z właściwością current. React obiecuje przekazać Ci dokładnie tę samą referencję do obiektu przy każdym renderowaniu. Nie śledzi on tego, co znajduje się w środku. Mutacja someRef.current odbywa się po cichu. React nie zareaguje.

Ta cisza jest kluczowa. Refy to „wyjście awaryjne” (escape hatch), a nie zamiennik stanu.

Podział w renderowaniu

Jeśli zmienisz stan, komponent zostanie ponownie wyrenderowany. Jest to zachowanie, którego spodziewa się większość początkujących, i jest to dokładnie to, czego potrzebujesz, gdy nowe dane muszą pojawić się na ekranie. Licznik, pole formularza, pobrana lista użytkowników — jeśli użytkownik to widzi, prawdopodobnie powinno to być w stanie (state). Cały przepływ danych w React opiera się na idei, że zmiany stanu powiadamiają renderer, aby zsynchronizował DOM.

Jeśli zmienisz ref, wizualnie nic się nie stanie. Zmienna aktualizuje się natychmiast i synchronicznie, ale komponent nie renderuje się ponownie. Dzięki temu refy są idealne dla wartości wspierających wewnętrzne działanie komponentu, które nie stanowią części wizualnego wyniku. Pomyśl o identyfikatorach timerów, migawkach (snapshots) poprzednich propsów czy bezpośrednich uchwytach DOM. Interfejs użytkownika (UI) nie przejmuje się identyfikatorem interwału, który steruje automatycznym odtwarzaniem karuzeli; interesuje go tylko to, który slajd jest widoczny. Identyfikator interwału powinien znajdować się w refie.

Kiedy stan jest właściwym narzędziem

Sięgnij po useState zawsze wtedy, gdy wartość jest częścią warstwy wizualnej Twojego UI.

Pola wejściowe to oczywisty przykład. Jeśli użytkownik wpisuje adres e-mail, a Ty musisz go zweryfikować i wyświetlić komunikat o błędzie pod polem, ten ciąg znaków e-mail musi być w stanie. Logika walidacji i baner błędu zależą od najnowszej wartości, a React wie, że ma zaktualizować baner tylko dlatego, że stan wywołał ponowne renderowanie.

Liczniki i przełączniki to kolejne podstawowe zastosowania. Przycisk zwiększający wynik, flaga otwarcia/zamknięcia modala, indeks karty — wszystkie te elementy przepływają przez stan, ponieważ wynik renderowania zmienia się wraz z wartością. Nawet wartości pochodne, takie jak przefiltrowana lista zależna od ciągu wyszukiwania, zazwyczaj zaczynają się od stanu, ponieważ wartość źródłowa jest widoczna dla użytkownika.

Warto również zrozumieć niuans związany z czasem. Aktualizacje stanu są asynchroniczne i grupowane (batched). Jeśli wywołasz setCount(count + 1) trzy razy wewnątrz jednego obsługiwania zdarzenia, React nie wyrenderuje komponentu trzy razy. Pogrupuje je w jedną aktualizację. Zmienna count wewnątrz trwającej funkcji również pozostaje nieaktualna aż do następnego renderowania. To grupowanie jest funkcją, która sprawia, że aplikacje działają szybko. Oznacza to jednak, że nie możesz oczekiwać, że zmienna stanu odzwierciedli nową wartość w następnej linii kodu.

Kiedy refy ratują sytuację

Używaj useRef do kwestii technicznych, a nie