Przechowywanie przyczyn, wyprowadzanie konsekwencji: projektowanie minimalnego stanu w React
Naucz się rozpoznawać zbędny stan w React, zastępuj łańcuchy synchronizacji oparte na Effect oraz flagi logiczne wartościami pochodnymi i uniami stanów, a także decyduj, gdzie powinien znajdować się stan.
Większość komponentów React nie staje się trudna do modyfikacji z powodu jednej złej decyzji. Stopniowo dochodzi do tego po jednym rozsądnym wywołaniu useState, aż ta sama informacja jest przechowywana w trzech miejscach i nikt nie może powiedzieć, która jej wersja jest autorytatywna. Ten przewodnik omawia realistyczną listę produktów, która wpada w tę pułapkę, a następnie pokazuje, jak zdecydować, co komponent powinien faktycznie pamiętać, co powinien obliczać przy każdym renderowaniu oraz gdzie powinno znajdować się każde pozostałe elementy stanu. Na końcu będziesz miał konkretną listę kontrolną do przeglądania stanu, aby uniknąć błędów synchronizacji.
Jak prosta lista produktów gromadzi stan
Wyobraź sobie wewnętrzną stronę administracyjną, która wyświetla produkty. Użytkownicy mogą wpisać nazwę w celu wyszukiwania, zawęzić listę do jednej kategorii, posortować ją według ceny, wybrać jeden produkt i zobaczyć, ile wyników pozostało. Pierwsza wersja przechowuje tylko to, co użytkownik wpisał i wybrał:
function Products({ products }) {
const [search, setSearch] = useState("");
const [category, setCategory] = useState("all");
// ...
}
Następnie pojawiają się żądania dotyczące nowych funkcjonalności. Tabela musi wyświetlać pasujące produkty, więc ktoś dodaje zmienną stanu przechowującą filtrowaną listę:
const [filteredProducts, setFilteredProducts] = useState(products);
Licznik nad tabelą pokazuje, ile produktów pasuje, a ta liczba również ma swój własny stan:
const [resultCount, setResultCount] = useState(products.length);
Gdy nie ma żadnych pasujących produktów, strona powinna wyświetlić komunikat o stanie pustym, więc dodaje się również odpowiedni flag:
const [hasResults, setHasResults] = useState(true);
Następnie następuje sortowanie, które obejmuje zarówno wybrany kryterium sortowania, jak i posortowaną kopię listy:
const [sortBy, setSortBy] = useState("name");
const [sortedProducts, setSortedProducts] = useState(products);
Każda zmiana jest mała i łatwa do zatwierdzenia podczas przeglądu. Kilka tygodni później jednak zaczynają pojawiać się zgłoszenia błędów. Przełączanie kategorii czasami pokazuje właściwe wiersze obok niewłaściwej liczby. Wyłączenie pola wyszukiwania na chwilę wyświetla komunikat „brak wyników”. Gdy nowa odpowiedź API dostarcza nowe products, tabela nadal pokazuje przestarzałą listę filtrowaną, dopóki użytkownik czegoś nie kliknie.
Komponent ten jest pełen stanów, a mimo to nie potrafi już odpowiedzieć na jedno kluczowe pytanie: która z tych wartości jest prawdziwa?
useState nie jest winny. Problemy pojawiają się, gdy komponent przechowuje kilka wersji informacji, które wszystkie można by obliczyć na podstawie mniejszego zbioru podstawowych faktów. Każda dodatkowa przechowywana wartość to kolejna rzecz, którą trzeba synchronizować z pozostałymi, a właśnie synchronizacja sprawia, że proste komponenty stają się kruche.
Każda przechowywana wartość to kolejny sposób na błąd
Komponenty potrzebują stanu, ponieważ niektóre informacje muszą przetrwać między renderowaniami: tekst w polu wprowadzania, aktywna karta, czy modala jest otwarta, który wiersz wybrał użytkownik. To naturalne zastosowania.
Błędem jest traktowanie stwierdzenia „to musi się pojawić na ekranie” tak, jakby oznaczało ono „to musi być przechowywane”. Spójrz ponownie na stronę produktu, gdzie każda wartość jest przechowywana w stanie:
const [search, setSearch] = useState("");
const [category, setCategory] = useState("all");
const [filteredProducts, setFilteredProducts] = useState(products);
const [resultCount, setResultCount] = useState(products.length);
const [hasResults, setHasResults] = useState(true);
Każda zmienna ma sensowną nazwę, ale nie są one od siebie niezależne. Lista przefiltrowana jest funkcją trzech wejść:
products + search + category
Liczba jest funkcją listy przefiltrowanej:
filteredProducts.length
A flaga stanu pustego jest funkcją liczby:
resultCount > 0
Niewiele prawdziwych faktów zostało rozszerzonych na kilka przechowywanych konsekwencji. To otwiera drogę do kombinacji, których interfejs nie powinien być w stanie wyświetlić, takich jak ta:
filteredProducts = []
resultCount = 4
hasResults = true
React chętnie przechowuje te wartości. Zadeklarowałeś trzy niezależne elementy stanu, więc React traktuje je jako takie. Utrzymanie ich logicznej spójności to wyłącznie zadanie twojej aplikacji, a każdy obsługiwacz zdarzeń, który ma dostęp do jednego z nich, musi pamiętać o pozostałych.
Dokumentacja React zaleca unikanie zbędnego i powtarzającego się stanu właśnie z tego powodu: gdy wartość może zostać obliczona na podstawie propów lub innego stanu podczas renderowania, przechowywanie jej oddzielnie tworzy jedynie nową możliwość niezgodności między kopiami.
Główny wniosek nie brzmi „wywoływaj useState rzadziej”. Chodzi o zmianę sposobu postrzegania tego, co stanowi stan:
Pamiętaj o faktach, których komponent nie może odzyskać w żaden inny sposób. Oblicz wszystko, co z nich wynika.
Zachowuj dane wejściowe w stanie i obliczaj resztę
Strona produktu nigdy nie musiała przechowywać wartości resultCount. Musi natomiast zapamiętać to, co użytkownik wpisał do pola wyszukiwania oraz jaką kategorię wybrał. Są to decyzje podejmowane przez osobę, a żadne dane produktu nie mogą ich odtworzyć. Wszystko inne wynika z tego.
function Products({ products }) {
const [search, setSearch] = useState("");
const [category, setCategory] = useState("all");
const filteredProducts = products.filter((product) => {
const matchesSearch = product.name
.toLowerCase()
.includes(search.toLowerCase());
const matchesCategory =
category === "all" || product.category === category;
return matchesSearch && matchesCategory;
});
const resultCount = filteredProducts.length;
const hasResults = resultCount > 0;
// ...
}
Zauważ, co zniknęło. Nie trzeba już aktualizować resultCount; hasResults nie ma metody ustawiania wartości, a także nie istnieje już żaden przypadek, w którym jakiś obsługiwacz odświeżałby filtrowaną listę, ale zapominał o jej długości. Każde renderowanie po prostu ponownie oblicza wyniki na podstawie bieżących danych wejściowych. Nowy ciąg znaków do wyszukiwania generuje nową listę, nowa kategoria również tworzy nową listę, a jeśli rodzic przekaże inny tablicę products, obliczenia po prostu wykorzystają tę nową tablicę.
Komponent ma teraz mniej zmiennych, które można edytować, co stanowi znacznie bardziej istotną poprawę niż zmniejszenie liczby linii kodu. Wartość pochodna może nadal zawierać błędy logiczne, ale nigdy nie stanie się przestarzała z powodu zapomnienia o jej odświeżeniu przez jakiegoś obsługiwacza. To eliminuje całą grupę stanów, do których komponent mógł wcześniej dojść.
Dokumentacja React ilustruje tę samą koncepcję za pomocą pola fullName tworzonego z imienia i nazwiska: jeśli można je obliczyć podczas renderowania, osobna zmienna stanu nie dodaje nic poza ryzykiem niezgodności.
Szybki test, który można przeprowadzić podczas przeglądu kodu:
If I deleted this state variable,
could I reconstruct its value exactly
from current props and other state?
Jeśli odpowiedź brzmi „tak”, zacznij od przekształcenia tego w zwykłe obliczenie. Stan służy do przechowywania informacji, a nie do przechowywania w pamięci ciągłych wyników pośrednich generowanych przez komponent.
Gdy useEffect staje się pipeline synchronizacji
Powszechną reakcją na przestarzały stan pochodny jest użycie useEffect, aby automatycznie aktualizować kopię tego stanu. Wtedy komponent produktowy wygląda mniej więcej tak:
const [filteredProducts, setFilteredProducts] = useState(products);
useEffect(() => {
const nextProducts = products.filter((product) => {
const matchesSearch = product.name
.toLowerCase()
.includes(search.toLowerCase());
const matchesCategory =
category === "all" || product.category === category;
return matchesSearch && matchesCategory;
});
setFilteredProducts(nextProducts);
}, [products, search, category]);
Następnie drugi efekt utrzymuje liczbę zgodną z listą:
useEffect(() => {
setResultCount(filteredProducts.length);
}, [filteredProducts]);
A może trzeci efekt steruje flagą stanu pustego:
useEffect(() => {
setHasResults(resultCount > 0);
}, [resultCount]);
Razem tworzą małą wewnętrzną ścieżkę przetwarzania:
products/search/category
↓
filteredProducts
↓
resultCount
↓
hasResults
Żaden z tych kroków nie komunikuje się z niczym poza Reactem. Przekształcają jedynie wartości, które React już posiada, a to właśnie ta różnica jest kluczowa. Dokumentacja React traktuje Effects jako środek ucieczki, służący do utrzymania komponentu zgodnego z czymś, czego React nie kontroluje – takim jak API przeglądarki, sokiet lub komponent niebędący częścią Reacta. Gdy Effect istnieje wyłącznie po to, by ustawić jeden element stanu komponentu w odpowiedzi na inny, obecna zalecenie polegają na zastanowieniu się, czy ten drugi element stanu w ogóle powinien istnieć.
Istnieje również koszt wykonywania, który łatwo przeoczyć. Każdy efekt jest uruchamiany po tym, jak React już zakończył renderowanie, więc każdy element łańcucha powoduje kolejne renderowanie z częściowo zaktualizowanymi wartościami. Właśnie stąd pochodzi krótki błysk „brak wyników” w początkowym scenariuszu: podczas jednego renderowania nowa lista istnieje, ale flaga nadal odzwierciedla stary licznik.
Wersja obliczona w ogóle nie ma żadnego łańcucha:
const filteredProducts = filterProducts(
products,
search,
category
);
const resultCount = filteredProducts.length;
const hasResults = resultCount > 0;
To nie tylko bardziej uporządkowana składnia – zmienia również to, o czym musisz myśleć. Dzięki przechowywanemu stanowi pochodnemu możesz śledzić, kiedy każda wartość została ostatnio zapisana, czy odpowiedni Effect już się wykonał, czy jego tablica zależności jest kompletna, oraz czy za nim wciąż czeka kolejna aktualizacja. Przy obliczeniach skupiasz się na wejściach i wyjściach, a w przypadku czystych transformacji jest to model o wiele łatwiejszy do utrzymania. Jeśli w twojej bazie kodu już istnieją takie Effects, krok po kroku opisany w artykule Jak przestać synchronizować stan za pomocą useEffect pokazuje, jak je bezpiecznie usunąć.
Zastąp flagi logiczne pojedynczym stanem
Duplikowane wartości to jeden z przykładów nadmiaru stanu. Inny problem pojawia się, gdy jeden koncept jest rozproszony między kilkoma niezależnymi flagami logicznymi. Klasycznym przykładem jest wysyłanie formularza:
const [isIdle, setIsIdle] = useState(true);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);
Przepływ powinien przebiegać dokładnie w jednej z czterech faz:
idle
submitting
success
error
Cztery zmienne logiczne mogą jednak reprezentować szesnaście kombinacji, z których wiele jest bezsensownych. Formularz może jednocześnie twierdzić, że wysyła dane i już odniósł sukces:
isSubmitting = true
isSuccess = true
Lub może jednocześnie zgłaszać sukces i niepowodzenie:
isSuccess = true
isError = true
Lub każda z tych zmiennych może mieć wartość false, co nie odpowiada żadnej z faz. Interfejs użytkownika może celowo unikać takich kombinacji, ale struktura danych na to pozwala, więc brak wywołania funkcji ustawiającej w jednym z obsługujących elementów wystarczy, by do nich dojść. Wskazówki React dotyczące strukturyzacji stanu wyraźnie zalecają unikanie takich sprzeczności oraz ograniczenie liczby zmiennych, które umożliwiają reprezentację niemożliwych stanów interfejsu.
Jedna wartość stanu opisuje ten pojęcie o wiele dokładniej. W TypeScript zbiór literówkowych ciągów znaków pozwala również kompilatorowi odrzucić błędy pisowni oraz nieznane fazy:
type Status =
| "idle"
| "submitting"
| "success"
| "error";
const [status, setStatus] = useState<Status>("idle");
Praktyczne wartości logiczne są nadal dostępne, teraz jako wartości pochodne:
const isSubmitting = status === "submitting";
const isSuccess = status === "success";
const isError = status === "error";
Różnica wydaje się niewielka, ale jest fundamentalna. Pierwsza wersja wymaga od kodu zachowania zgodności czterech faktów. Druga przechowuje jeden fakt i odczytuje cztery jego wersje.
Korzyści rosną wraz z komponentem. Proces zakupu może przechodzić przez te fazy:
editing
validating
submitting
confirmed
failed
Importer plików może przechodzić przez te fazy:
idle
uploading
processing
completed
failed
Gdy komponent ma tryby, które wykluczają się wzajemnie, należy uczynić to wykluczenie częścią modelu stanu zamiast regułą, której musi przestrzegać każdy obsługiwacz. To właśnie ty decydujesz, jakie stany program może reprezentować, a ta decyzja wymaga takiej samej uwagi jak sam kod. Jedna zastrzeżenie: jeśli jakaś faza zawiera dane, takie jak komunikat o błędzie istniejący tylko w fazie failed, zastosowanie zjednoczenia dyskryminowanego obiektów pozwala utrzymać te dane przy odpowiedniej fazie, zamiast dodawać kolejną niepowiązaną z nią zmienną.
Mniej zmiennych to nie to samo co jeden duży obiekt
Gdy zespół usłyszy „zmniejszyć liczbę stanów”, kuszącą nadkorektą jest umieszczenie wszystkiego w jednym obiekcie:
const [state, setState] = useState({
search: "",
category: "all",
selectedProductId: null,
sidebarOpen: false,
page: 1,
});
To nie jest automatycznie ulepszenie. Odrębne wywołania useState nie wiążą się z żadnym znaczącym kosztem, więc minimalizacja ich liczby nie jest celem. Celem jest jasne przedstawienie niezależnych informacji oraz unikanie przechowywania tej samej wartości dwukrotnie.
search i category zmieniają się według własnych harmonogramów, a sidebarOpen nie ma z nimi nic wspólnego. Zachowanie ich jako odrębnych zmiennych sprawia, że każda aktualizacja jest oczywista w miejscu jej wywołania:
const [search, setSearch] = useState("");
const [category, setCategory] = useState("all");
const [selectedProductId, setSelectedProductId] =
useState<string | null>(null);
const [sidebarOpen, setSidebarOpen] = useState(false);
Dokumentacja React podchodzi w ten sam sposób. Warto grupować wartości, które zawsze zmieniają się razem, natomiast zbędne, sprzeczne, powtarzające się lub głęboko zagnieżdżone dane należy uprościć. Łączenie niespowiązanych wartości ma również praktyczną wadę: przy każdej aktualizacji konieczne jest rozszerzenie poprzedniego obiektu, a zaniedbanie tego spowoduje bezgłośne usunięcie pozostałych pól.
Zatem istotne pytanie brzmi nie to, czy zbiór wartości może zmieścić się w jednym obiekcie – prawie wszystko da radę. Lepiej zadać pytanie:
Czy te wartości tworzą spójną całość stanu, której przejścia należą do siebie?
Gdy tak jest, ich grupowanie może ułatwić zrozumienie kodu. Gdy nie, połączony obiekt jedynie utrudnia zrozumienie, która aktualizacja wpływa na co. Redukcja stanu polega na eliminacji informacji przechowywanych dwukrotnie, a nie na zmieszczaniu komponentu w jak najmniejszej liczbie Hooków.
Pobieranie wartości bez ignorowania wydajności
Umieszczenie z filtrowaną listą poza stanem zwykle wywołuje jedną zastrzeżenie: czy filtr nie będzie teraz wykonywany przy każdym renderowaniu? Rzeczywiście tak jest, a w przypadku większości codziennych transformacji to dokładnie właściwa cena. Filtrowanie tablicy o umiarkowanej wielkości jest tanie, a jego wykonywanie bezpośrednio w kodzie utrzymuje komponent prosty, bez żadnych zauważalnych kosztów.
Jeśli analiza wykazuje, że dana transformacja jest rzeczywiście kosztowna – na przykład duża lista, która jest filtrowana i sortowana – można zapamiętać jej wynik za pomocą useMemo:
const filteredProducts = useMemo(() => {
return products
.filter((product) => {
const matchesSearch = product.name
.toLowerCase()
.includes(search.toLowerCase());
const matchesCategory =
category === "all" ||
product.category === category;
return matchesSearch && matchesCategory;
})
.sort(compareProducts);
}, [products, search, category, sortBy]);
Należy zwrócić uwagę na to, czego useMemo nie zmienia. filteredProducts ponownie nie stał się stanem zapisywalnym. Nadal jest to czysta funkcja swoich wejść; memoizacja decyduje jedynie o tym, czy React może ponownie wykorzystać poprzedni wynik podczas danej renderizacji zamiast go ponownie obliczać. Dzięki temu poprawność i optymalizacja pozostają oddzielnymi kwestiami. Dokumentacja React przedstawia useMemo wyłącznie jako narzędzie do optymalizacji wydajności i ostrzega przed poleganiem na nim dla zapewnienia poprawnego działania, ponieważ React może usunąć zapamiętane wartości.
Kolejność działań wynikająca z tego jest następująca:
First make the state model correct.
Then measure.
Then optimize expensive calculations if necessary.
Używanie stanu jako ręcznie zarządzanej pamięci podręcznej odwraca tę kolejność. Dodaje to złożoność synchronizacji już na początku, zanim ktokolwiek udowodni, że obliczenia są wolne. Warto również sprawdzić, czy obliczenie zapisane w pamięci podręcznej wymienia wszystkie dane wejściowe w swojej tablicy zależności; w powyższym przykładzie sortBy jest wymienione, ponieważ oczekuje się, że porównywacz sortowania będzie od niego zależeć.
Umieść każdą część stanu tam, gdzie podejmowane są decyzje
Nawet stan, który rzeczywiście musi istnieć, może sprawiać problemy, jeśli znajduje się w niewłaściwym komponencie. Załóżmy, że każdy wiersz produktu śledzi własny wybór:
function ProductRow({ product }) {
const [selected, setSelected] = useState(false);
// ...
}
To działa dopóki każdy wiersz może być przełączany niezależnie. Teraz wymagania się zmieniają: jednocześnie można wybrać tylko jeden produkt. Nagle kilka komponentów rodzinnych posiada po swojej kopii tego, co powinno być jedną wspólną informacją, mianowicie który produkt jest wybrany. Gdy ta informacja ma znaczenie dla kilku komponentów rodzinnych, ich rodzic powinien nią zarządzać:
function ProductTable({ products }) {
const [selectedProductId, setSelectedProductId] =
useState<string | null>(null);
return products.map((product) => (
<ProductRow
key={product.id}
product={product}
selected={product.id === selectedProductId}
onSelect={() => setSelectedProductId(product.id)}
/>
));
}
Wiersze w ogóle już nie przechowują informacji o wyborze. Otrzymują wartość logiczną i funkcję zwrotną, a istnieje dokładnie jedno źródło prawdy:
selectedProductId
Dokumentacja React przedstawia to jako przydzielanie każdemu odrębnemu elementowi stanu dokładnie jednego komponentu właściciela. Gdy kilka komponentów musi koordynować działania w oparciu o te same informacje, przeniesienie ich do najbliższego wspólnego rodzica zapobiega rozbieżnościom w kopiach tych informacji.
Żadne z tych argumentów nie przemawia za umieszczaniem wszystkiego u korzenia aplikacji. Należy stwierdzić, że inne elementy powinny pozostać lokalne. To, czy podpowiedź jest widoczna, nie powinno znajdować się obok danych autoryzacyjnych, a półużywane pola formularza rzadko wymagają globalnego przechowywania. Stan jest najłatwiejszy do zarządzania, gdy jego właściciel odpowiada stopniu rozpowszechnienia decyzji, na której opiera się. Jeśli umieścić go zbyt nisko, komponenty będą powtarzać tę samą informację; jeśli zbyt wysoko, odległe części aplikacji będą ponownie renderować się na skutek zmian, które są dla nich nieistotne. Znalezienie tej granicy stanowi istotny element dobrego projektowania stanu, a Rethinking React State: Where Your Data Should Actually Live szczegółowo omawia opcje lokalne, wspólne, serwerowe oraz oparte na URL.
Reducerzy organizują przejścia, a nie model
Gdy komponent zawiera wiele funkcji do ustawiania wartości, kolejnym krokiem jest często przejście na useReducer, co zazwyczaj jest dobrym rozwiązaniem. Zamiast funkcji obsługi, która wykonywałaby kilka powiązanych ze sobą wywołań w ten sposób:
setStatus("submitting");
setError(null);
setLastAttempt(Date.now());
opisuje się to, co się wydarzyło, jako pojedyncze zdarzenie:
dispatch({ type: "submitted" });
Reduktor gromadzi wszystkie zmiany w jednym miejscu, co jest przydatne, gdy kilka ściśle powiązanych wartości zmienia się jednocześnie. Nie może jednak sprawić, by dane zbędne przestały być zbędnymi. Ten początkowy stan nadal budzi podejrzenia:
const initialState = {
search: "",
products: [],
filteredProducts: [],
resultCount: 0,
hasResults: true,
};
Umieszczanie powtarzających się wartości w reduktorze nie rozwiązuje problemu synchronizacji – jedynie przenosi logikę synchronizacji do reduktora. Każda akcja może dziś poprawnie aktualizować wszystkie kopie, ale model nadal pozwala na przechowywanie kilku wersji tej samej informacji, a kolejna dodana akcja może pominąć jedną z nich.
Lepszy reducer przechowuje tylko dane wejściowe:
const initialState = {
search: "",
category: "all",
sortBy: "name",
};
Listę widocznych produktów generuje się następnie podczas renderowania na podstawie stanu reduktora oraz aktualnych wartości zmiennych products. Funkcja useReducer jest przydatna, gdy przejścia między stanami stają się skomplikowane, ale nie zastępuje podstawowego pytania o to, co komponent faktycznie musi zapamiętać. Najpierw odpowiedz na to pytanie, a dopiero potem wybierz narzędzie do zarządzania tym stanem.
Lista kontrolna przy sprawdzaniu stanu komponentu
useState sprawia, że dodawanie stanu jest niemal bezproblemowe, a ta łatwość ukrywa koszty architektoniczne. Każda nowa zmienna to kolejna wartość, która może ulec zmianie samodzielnie. Jeśli kopiauje coś już dostępnego, konieczne stają się zasady utrzymywania synchronizacji obu wersji. Jedna kopia jest łatwa do zarządzania, natomiast pięć takich kopii powoduje plątaninę efektów i list zależności, funkcji ustawiających, które uruchamiają inne funkcje ustawiające, kodu służącego do resetowania, nieaktualnych odczytów, sprzecznych flag oraz błędów, które pojawiają się dopiero po określonej sekwencji kliknięć. Rozwiązaniem rzadko jest bardziej zaawansowany mechanizm synchronizacji; zazwyczaj synchronizacja w ogóle nie powinna istnieć.
Gdy stan komponentu stale rośnie, przejrzyj każdą przechowywaną wartość i zadać sobie pytania:
- Czy reprezentuje ona decyzję podjętą przez użytkownika lub system?
- Czy komponent musi ją pamiętać między kolejnymi renderowaniami?
To pytania mówią o wiele więcej niż tylko podsumowanie użycia Hooków. Komponent zawierający osiem niezależnych, niezbędnych elementów stanu może być doskonale zaprojektowany, podczas gdy ten z trzema zmiennymi ma już za dużo informacji, jeśli dwie z nich są kopiami lub wynikiem trzeciej.
Główne wnioski
- Zachowuj przyczyny, takie jak dane wprowadzone przez użytkownika i wybory; w trakcie renderowania oblicz konsekwencje, takie jak filtrowane listy, liczby i flagi.
- Effect, który ustala stan wyłącznie na podstawie innego stanu, jest oznaką, że ta druga wartość powinna być wynikiem obliczeń.
- Przedstaw model wzajemnie wykluczających się trybów jako jedną wartość stanu, aby nie dało się reprezentować niemożliwych kombinacji.
- Grupuj wartości tylko wtedy, gdy zmieniają się razem; jeden duży obiekt sam w sobie nie jest celem.
- Zastosuj
useMemopo pomiarach i pamiętaj, że jest to bufor, a nie źródło prawdy. - Dla wspólnych faktów wybierz jednego właściciela na najniższym wspólnym rodzicu i utrzymuj wyłącznie lokalny stan interfejsu w tym samym miejscu.
- Gdy komponent staje się trudny do modyfikacji, zastanów się, jakie fakty z rzeczywistości reprezentuje każda zmienna stanu, zanim dodasz kolejny funkcję ustawiania. Stan, który najłatwiej jest synchronizować, to ten, którego w ogóle nie przechowywałeś.
Literatura pokrewna
- React Query i Redux: Przemyślenie o stanie serwera w dużych aplikacjach — Dowiedz się, dlaczego aplikacja do czatowania w środowisku produkcyjnym używała TanStack Query zamiast Redux do zarządzania danymi serwera oraz gdzie Redux nadal ma swoje zastosowanie w nowoczesnej architekturze React.
- Przemyślenie o stanie React: Gdzie naprawdę mają znaleźć się twoje dane — Ten artykuł wyjaśnia, jak zmniejszyć liczbę błędów w React poprzez przenoszenie stanu do URL, DOM lub wartości pochodnych zamiast nadmiernego używania useState.