Menu

Podnoszenie stanu w React: wspólny stan komponentów

Podnoszenie stanu oznacza przeniesienie stanu z dwóch komponentów do ich najbliższego wspólnego rodzica, który przekazuje wartość i setter z powrotem w dół jako propsy. Dowiedz się, kiedy podnosić stan, jak zrobić to w trzech krokach i kiedy zostawić stan lokalnie.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Podnoszenie stanu oznacza przeniesienie stanu z komponentów, które muszą go współdzielić, do ich najbliższego wspólnego rodzica. Rodzic trzyma wartość, przekazuje ją w dół jako prop i przekazuje funkcję, którą dzieci wywołują, żeby ją zmienić, więc każde dziecko renderuje się z jednej kopii stanu.

Kliknij "Show" na dowolnym panelu: otwiera się, a ten, który był otwarty, się zamyka. Naraz może być otwarty tylko jeden panel, bo panele nie decydują o tym same. App trzyma openIndex, a każdy Panel dostaje tylko isOpen i funkcję onOpen.

Problem: stan, który powinien się zgadzać

Zacznij od oczywistej wersji, w której każdy panel ma własny stan isOpen. Każdy panel działa, ale panele nic o sobie nie wiedzą, więc nic nie powstrzymuje dwóch z nich przed jednoczesnym otwarciem.

Otwórz oba panele: zostają otwarte razem. Stan w React jest prywatny dla komponentu, który go deklaruje, więc komponent rodzeństwa nie może go odczytać ani zresetować. Gdy dwa komponenty muszą się zgadzać, stan musi żyć nad nimi oboma.

Podnoszenie stanu w trzech krokach

Przerobienie drugiego przykładu na pierwszy wymaga trzech zmian.

  1. Usuń stan z dziecka. Usuń useState w Panel i zamiast tego odczytuj isOpen z propsów. Dziecko już nie decyduje, czy jest otwarte.
  2. Przekaż z rodzica wartość i sposób jej zmiany. Panel dostaje isOpen i callback onOpen. Dziecko wywołuje onOpen() po kliknięciu przycisku; nie wie, co rodzic z tym zrobi.
  3. Dodaj stan do wspólnego rodzica. App deklaruje openIndex i zamienia go w propsy dla każdego panelu: isOpen={openIndex === 1} i onOpen={() => setOpenIndex(1)}.

Najbliższy wspólny rodzic to najniższy komponent, który renderuje wszystkie komponenty potrzebujące stanu. Tutaj to App. Gdyby panele znajdowały się w komponencie Faq, stan trafiłby do Faq, a nie wyżej.

Po podniesieniu stanu Panel jest kontrolowany przez rodzica w tym samym sensie co pole kontrolowane: pokazuje to, co mówią propsy, i zgłasza zmiany przez callback. Strona o komponentach kontrolowanych i niekontrolowanych omawia ten sam pomysł dla elementów formularzy.

Jedno źródło prawdy

Gdy dwie części ekranu pokazują ten sam fakt, zapisz go raz, a wszystko inne z niego wyliczaj. Klasycznym przypadkiem jest przelicznik temperatury: pola Celsjusza i Fahrenheita zawsze muszą się zgadzać, więc nie mogą trzymać każde własnej liczby.

Pisz w którymkolwiek polu, a drugie za nim podąża. Stan to jeden fakt: liczba, którą użytkownik wpisał ostatnio, i skala, w której była. Drugie pole jest z niego wyliczane podczas renderowania, więc pole, w którym piszesz, zawsze zachowuje dokładnie to, co wpisałeś. Zmień stan początkowy na { value: '212', scale: 'f' }, a komunikat pod polami zmieni się na "Water boils."

Trzymanie liczby w stopniach Celsjusza i liczby w stopniach Fahrenheita w dwóch kawałkach stanu oznaczałoby, że każdy handler musi aktualizować obie, a gdy pierwszy handler o tym zapomni, pola przestaną się zgadzać. Jedna zapisana wartość nie może nie zgadzać się sama ze sobą.

Przekazywanie settera w dół

Dziecko może zmienić stan rodzica tylko przez funkcję, którą rodzic mu daje. Możesz przekazać sam setter (onSelect={setColor}) albo funkcję, która robi więcej (onOpen={() => setOpenIndex(1)}). Nadanie propsowi nazwy w stylu zdarzenia, takiej jak onSelect czy onChange, a nie setSelected, sprawia, że dziecko nie wie, jak rodzic przechowuje wartość, więc rodzic może to później zmienić bez ruszania dziecka.

ColorPicker i Preview nigdy ze sobą nie rozmawiają. Picker zgłasza wybór w górę, App go zapisuje, a nowa wartość płynie w dół do obu.

Kiedy nie podnosić stanu

Podnoszenie ma swój koszt. Gdy stan żyje w rodzicu, każda zmiana renderuje rodzica i domyślnie wszystkie jego dzieci, także te, które stanu nie używają. Podnoś stan tylko do najbliższego wspólnego rodzica, a stan używany przez jeden komponent zostaw w tym komponencie.

Pisz w polu i obserwuj konsolę: przy każdym naciśnięciu klawisza renderuje się tylko SearchBox. Teraz przenieś query do App i przekaż je do SearchBox jako propsy. Każde naciśnięcie klawisza wypisuje wtedy też ProductList, choć lista nie używa zapytania. Gdyby lista filtrowała po zapytaniu, podniesienie byłoby właściwą decyzją, bo oba komponenty zależałyby wtedy od tej samej wartości.

Pytanie, które warto zadać, brzmi „kto musi odczytać tę wartość?". Jeśli odpowiedź to jeden komponent, stan zostaje w nim. Jeśli kilka, trafia do ich najbliższego wspólnego rodzica.

Gdy podnoszenie idzie za daleko

Czasem najbliższy wspólny rodzic jest wysoko w drzewie, a wartość trzeba przekazać przez kilka komponentów, które tylko podają ją dalej. To jest prop drilling. Kilka warstw propsów jest w porządku i łatwo je prześledzić. Gdy ta sama wartość wędruje przez wiele warstw albo potrzebuje jej prawie każdy komponent (zalogowany użytkownik, motyw, język), odczytuj ją przez useContext, zamiast przekazywać ręcznie. Kontekst zmienia sposób, w jaki wartość dociera do dzieci; sam stan nadal żyje w jednym rodzicu, więc nadal jest podniesiony.

Najczęściej zadawane pytania

Co oznacza podnoszenie stanu w React?

Przeniesienie kawałka stanu z komponentów, które go używają, do ich najbliższego wspólnego rodzica. Rodzic jest właścicielem stanu i przekazuje dzieciom w dół jako propsy wartość oraz funkcję do jej zmiany.

Jak dwa sąsiednie komponenty mogą współdzielić stan w React?

Rodzeństwo nie może czytać nawzajem swojego stanu. Umieść stan we wspólnym rodzicu, przekaż wartość obu komponentom, a setter (albo handler taki jak onChange) temu, który ją zmienia. Oba komponenty renderują się wtedy z tej samej wartości.

Jak komponent dziecka aktualizuje stan rodzica?

Rodzic przekazuje funkcję jako prop, na przykład onSelect={setSelected} albo onSelect={(id) => setSelected(id)}, a dziecko ją wywołuje. Stan zostaje w rodzicu; dziecko tylko prosi o zmianę.

Kiedy nie podnosić stanu?

Gdy stanu używa tylko jeden komponent. Podniesienie go wyżej, niż trzeba, sprawia, że rodzic renderuje się przy każdej zmianie, a propsy rozchodzą się przez komponenty, których to nie dotyczy. Trzymaj stan jak najbliżej miejsca, w którym jest używany.

Jaka jest alternatywa dla podnoszenia stanu za wysoko?

Jeśli przekazujesz te same propsy przez wiele warstw, odczytuj wspólną wartość przez kontekst (useContext) albo zmień strukturę tak, żeby komponenty, które jej potrzebują, były bliżej siebie.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ