Przemyślenie stanu w React: gdzie powinny faktycznie znajdować się twoje dane
Mniej błędów w React dzięki mniejszemu użyciu useState: przenieś stan do adresu URL, DOM-u lub wartości pochodnych oraz zdecyduj, kto jest odpowiedzialny za każdą część, zanim zaczniesz korzystać z mechanizmów zarządzania stanem.
Źródła: Stop Adding React State: It’s Creating Bugs You Don’t Need
Przedstawiciel sprzedaży w rozwijającej się firmie typu SaaS spędza znaczną część poranka na przeglądaniu bazy danych klientów zawierającej dziesięć tysięcy kont.
Po dokładnym filtrowaniu wyodrębnia klientów korporacyjnych z Europy, których subskrypcje nie zostały odnowione w ciągu ostatnich dziewięćdziesięciu dni.
W rezultacie powstaje dokładna lista trzydziestu kont wymagających natychmiastowej uwagi.
Bierze adres URL bezpośrednio z przeglądarki i wysyła go przez Slack do swojego kierownika regionalnego wraz z notatką:
">Oto konta o najwyższym priorytecie na dziś rano."
Kierownik otwiera link.
Strona się załadowuje.
Jednak zamiast sfiltrowanej listy pokazuje standardowy widok: wszystkie dziesięć tysięcy kont, niefiltrowane i niesortowane.
Całe to staranne filtrowanie zniknęło, ponieważ inżynier, który stworzył tę stronę, przechowywał każdy filtr, pole sortowania oraz wartość paginacji w lokalnym stanie komponentu za pomocą useState.
Scenariusz ten ilustruje wzorzec, który stale pojawia się w kodach napisanych w React.
Mamy tendencję do korzystania ze stanu po prostu dlatego, że React ułatwia jego tworzenie.
Potrzebna jest wartość filtru?
useState.
Należy śledzić, który zakładka jest aktywna?
useState.
Potrzebny jest licznik?
useState.
Należy przechowywać pole formularza?
useState.
Należy śledzić różne etapy wywołania API?
To kolejne trzy wywołania useState.
Niedługo potem jeden komponent musi zarządzać dziesiątkami wartości, które tak naprawdę od początku nie musiały znajdować się w stanie React.
Sam stan nie jest tutaj wrogiem. Każde złożone aplikacje React potrzebują go.
Prawdziwym problemem jest umieszczanie stanu w miejscu, gdzie nie powinien być.
Każda dodatkowa zmienna stanu to kolejna rzecz, którą aplikacja musi synchronizować z całym resztą.
Jeśli tego nie kontrolować, może to prowadzić do różnych problemów, między innymi:
- Dodatkowa praca potrzebna do utrzymywania zgodności duplikatów wartości
- Dane, które stają się przestarzałe w jakimś cache’u
- Wartości wyświetlane na ekranie, które już nie odpowiadają rzeczywistości
- Renderowanie, które następuje mimo braku faktycznych zmian
- Błędy czasowe, gdy dwie aktualizacje kolidują w nieprzewidywalny sposób
- Efekty, które stają się coraz bardziej skomplikowane w próbach utrzymania spójności
- Błędy, których źródło jest trudniejsze do zlokalizowania
- Strona, która zapomina o wszystkim zaraz po ponownym załadowaniu
Pisanie doskonałego kodu w React nie polega tak naprawdę na opanowaniu złożonej logiki stanu.
Często chodzi o to, by rozpoznać, kiedy w ogóle nie należy tworzyć danego stanu.
1. URL to najlepszy menedżer stanu w Twojej aplikacji
Wyobraź sobie typowy ekran aplikacji pełen danych.
Prawdopodobnie musi on śledzić:
- Szukanie
- Filtrowanie
- Sortowanie
- Paginacja
- Aktywne karty
- Wybrane kategorie
- Odległości czasowe
Zwykle stosowanym podejściem jest umieszczenie tego wszystkiego w stanie komponentu.
function ProductList() {
const [activeCategory, setActiveCategory] = useState('all');
const [currentPage, setCurrentPage] = useState(1);
const [searchQuery, setSearchQuery] = useState('');
// Fetch and filter data...
}
Na pierwszy rzut oka wydaje się to w pełni w porządku.
Następnie użytkownik naciska „Odśwież”.
Każda wartość wraca do swojej domyślnej.
Lub udostępniają link koleżeńskiemu.
Kolega ten trafia na zupełnie inny widok.
Lub klikają przycisk „Wstecz” w przeglądarce.
Filtracja, którą ustawili, może nie zostać przywrócona, ponieważ stan React nie ma wbudowanego połączenia z historią nawigacji przeglądarki.
Sam useState nie jest tu winny.
Prawdziwym problemem jest to, że URL już istnieje jako mechanizm do reprezentowania stanu nawigacyjnego, a jest on całkowicie pomijany.
Zamiast pisać:
const [currentPage, setCurrentPage] = useState(1);
const [searchQuery, setSearchQuery] = useState('');
const [category, setCategory] = useState('all');
można bezpośrednio zakodować te same informacje w URL.
Coś w rodzaju:
/products?category=enterprise&page=3&query=react
Używając biblioteki routingu takiej jak Next.js, React Router lub TanStack Router, można odczytać te parametry bezpośrednio z URL:
import { useSearchParams } from 'next/navigation';
function ProductList() {
const searchParams = useSearchParams(); const category = searchParams.get('category') || 'all';
const page = Number(searchParams.get('page')) || 1;
const query = searchParams.get('query') || ''; // Fetch data using the URL state...
}
To niewielkie zmiany drastycznie wpływają na cykl życia tego stanu.
Odświeżyć stronę?
Filtrzy pozostają nienaruszone.
Podzielić się linkiem?
Każdy, kto go otworzy, zobaczy dokładnie tę samą widok.
Zapisać jako zakładkę?
Później zostanie naładowana w tym samym stanie.
Kliknąć „Wstecz”?
Brauzer poprawnie przechodzi między poprzednimi stanami.
To znacząca zaleta pod względem architektury.
Stan URL daje więcej niż tylko możliwość przechowywania danych
Przetrwanie odświeżenia strony to tylko część korzyści.
Większą zaletą jest to, że URL staje się przenośnym zrzutem obrazu aktualnego stanu aplikacji, który można podzielić z kimkolwiek.
Weźmy jako przykład panel sterowania obsługą klienta:
/support/tickets?status=open&priority=high&team=billing
Jeden menedżer może natychmiast wysłać ten dokładny link innemu menedżerowi.
Panel sterowania skupiony na sprzedaży może wyglądać tak:
/accounts?region=europe&plan=enterprise&renewal=90
Narzędzie analityczne może używać czegoś w rodzaju:
/analytics?range=30d&metric=revenue&groupBy=region
Żaden z tych nie jest dowolnym łańcuchem zapytania.
Każdy z nich to głęboki link wskazujący na konkretny, możliwy do odtworzenia stan aplikacji.
Dokładnie do tego służą adresy URL.
Kiedy stan w URL ma sens
Parametry URL doskonale nadają się do przechowywania takich informacji jak:
- To, co użytkownik wpisał do pola wyszukiwania
- Aktywne wybory filtrów
- Kolumny i kierunek sortowania danych
- Strona wyników, która jest obecnie pokazywana
- Bieżąco wybrana karta
- Wybrany zakres dat
- Obecnie otwarty zapisany widok
- Ustawienia raportu wygenerowanego automatycznie
- Dostosowana konfiguracja na panelu sterowania
- Każdy stan interfejsu, który użytkownik może chcieć przekazać komuś innemu
Mimo to nie wszystko pasuje do tego formatu.
Haseła oczywiście nie powinny znajdować się w adresie URL.
Również stan tymczasowej animacji modalnej nie powinien być przechowywany.
Ogólnie mówiąc, nie powinien być to też duży, głęboko zagnieżdżony obiekt.
Pytanie przewodnie jest proste:
Zadaj sobie pytanie, czy użytkownik miałby uzasadnione oczekiwania, że ta widok będzie możliwy do udostępnienia, zaznaczania jako ulubiony lub przywrócenia — jeśli tak, URL jest prawdopodobnie odpowiednim miejscem na przechowywanie tego stanu.
2. Przestań przechowywać wartości, które można łatwo obliczyć
Szybkim sposobem na zmniejszenie ilości stanu w React jest zaprzestanie przechowywania wartości, które w rzeczywistości pochodzą z innych wartości.
Weźmy ten przykład:
const [items, setItems] = useState([]);
const [itemCount, setItemCount] = useState(0);
A gdzie indziej w komponencie:
setItems(newItems);
setItemCount(newItems.length);
Masz teraz dwie oddzielne zmienne stanu opisujące tę samą rzecz.
Jeśli items zostanie zaktualizowany, ale itemCount nie pójdzie w jego ślady, twoja interfejs pokaże nieprawidłowe dane.
Stworzyłeś problem z synchronizacją, który w ogóle nie musiał istnieć.
Czystsza wersja wygląda tak:
const [items, setItems] = useState([]);
const itemCount = items.length;
Teraz istnieje jedyny źródło prawdy.
Liczba jest obliczana za każdym razem, gdy komponent jest renderowany.
Ta sama logika ma zastosowanie przy filtrowaniu kolekcji.
Zamiast pisać:
const [items, setItems] = useState([]);
const [activeItems, setActiveItems] = useState([]);
zwykle można po prostu napisać:
const activeItems = items.filter(item => item.active);
Lub:
const totalPrice = items.reduce(
(total, item) => total + item.price,
0
);
Lub:
const hasErrors = fields.some(field => field.error);
Lub:
const completedCount = todos.filter(todo => todo.completed).length;
Pytanie, które warto zadać, to nie jest:
">Czy mogę to technicznie przechowywać w stanie?"
Oczywiście, że możesz.
To, o co powinieneś zapytać, to:
">Czy ta wartość ma własny, odrębny cykl życia?"
Jeśli nie, prawdopodobnie nie powinna być częścią stanu.
3. Przestań łączyć każde pole formularza ze stanem
Kolejnym nawykiem, którego warto się pozbyć, jest traktowanie każdego pola formularza jako odrębnej zmiennej stanu.
Niewielki formularz zbudowany w ten sposób może wyglądać tak:
function ContactForm() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); submitData({
firstName,
lastName,
email,
message
});
}; return (
<form onSubmit={handleSubmit}>
<input
value={firstName}
onChange={e => setFirstName(e.target.value)}
/> <input
value={lastName}
onChange={e => setLastName(e.target.value)}
/> <input
value={email}
onChange={e => setEmail(e.target.value)}
/> <textarea
value={message}
onChange={e => setMessage(e.target.value)}
/> <button type="submit">Send</button>
</form>
);
}
Technicznie nie ma tu nic błędnego.
Wprowadzenia kontrolowane są przydatne wtedy, gdy aplikacja naprawdę musi reagować na to, co wpisuje użytkownik. Oto kilka sytuacji, w których jest to istotne:
- Weryfikacja poprawności pola w trakcie wpisywania
- Oferowanie sugestii w stylu autodopasowywania
- Modyfikacja formatu tekstu w czasie rzeczywistym
- Liczenie pozostałych znaków
- Pokazywanie lub ukrywanie innych pól w zależności od wprowadzonych danych
- Ocena siły hasła
- Łączenie zachowania jednego pola z wartością innego
Jednak często aplikacja nie ma powodu, by przejmować się tym, co wpisał użytkownik, dopóki nie naciśnie Submit.
W takich przypadkach React nie musi śledzić każdej litery w miarę jej wpisywania.
Brauzer już zajmuje się tym zadaniem.
Pozwól brzegownikowi zająć się tym
Rodzinne formularze HTML zarządzają wartościami wprowadzanymi od dziesięcioleci.
Możesz skorzystać z tego wbudowanego zachowania przy użyciu FormData:
function ContactForm() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget); const payload = {
firstName: formData.get('firstName') as string,
lastName: formData.get('lastName') as string,
email: formData.get('email') as string,
message: formData.get('message') as string,
}; submitData(payload);
}; return (
<form onSubmit={handleSubmit}>
<input name="firstName" required />
<input name="lastName" required />
<input name="email" type="email" required />
<textarea name="message" required /> <button type="submit">Send</button>
</form>
);
}
Dzięki temu wzorcowi React nie potrzebuje już osobnego pola stanu dla każdego pola.
Brauzer przechowuje to, co wpisał użytkownik.
React po prostu odczytuje te wartości po wysłaniu formularza.
Jest to szczególnie przydatne w przypadku dużych formularzy.
Pomyśl o procesie onboardingu dla przedsiębiorstwa z 30 polami.
Czy naprawdę chciałbyś mieć 30 oddzielnych zmiennych stanu tylko po to, by przechowywać to, co ktoś wpisał?
Prawdopodobnie nie.
Wejścia kontrolowane i niekontrolowane mogą współistnieć
To wcale nie oznacza, że komponenty kontrolowane są błędem.
One nie są.
Istnieje wiele sytuacji, w których kontrola danych wejściowych jest dokładnie tym, co należy zrobić.
Cel polega po prostu na porzuceniu założenia, że React musi domyślnie kontrolować wartość każdego pola.
Jeśli twoja aplikacja nie potrzebuje tych danych aż do wysłania formularza, często wystarczy pozwolić na ich przechowywanie w standardowym formularzu.
Rozróżnienie to może znacznie uproszczyć duże formularze.
4. Dlaczego flagi logiczne zawodzą
Kolejny częsty problem pojawia się ciągle w rzeczywistych bazach kodu.
Rozważmy typowe wezwanie API.
Powszechnym rozwiązaniem jest śledzenie tego stanu za pomocą trzech oddzielnych flag logicznych:
const [isLoading, setIsLoading] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);
Na początku wszystkie trzy są fałszywe:
loading = false
success = false
error = false
Gdy tylko rozpoczyna się żądanie:
loading = true
Gdy zostanie ono pomyślnie zakończone:
loading = false
success = true
Na pierwszy rzut oka wydaje się to rozsądne.
Ale załóżmy, że gałąź obsługująca błędy nie zdoła sformatować wartości isLoading.
Możliwe jest, że otrzymasz:
loading = true
error = true
Albo nieodpowiednio zaprojektowany mechanizm ponawiania prób może spowodować, że otrzymasz:
success = true
error = true
W tym momencie komponent znajduje się w stanie, który logicznie nie ma sensu.
Czy powinien wyświetlać komunikat o sukcesie?
Czy powinien wyświetlać informację o błędzie?
Czy nadal powinien pokazywać ikonkę spinera?
Z technicznego punktu widzenia wszystkie trzy wartości logiczne mogą jednocześnie uzasadniać odpowiedź „tak”.
To właśnie ta niejednoznaczność stanowi błąd.
Lepiej traktuj to jako maszynę stanów skończonych
Czystsze podejście:
type RequestStatus =
| 'idle'
| 'loading'
| 'success'
| 'error';
const [status, setStatus] =
useState<RequestStatus>('idle');
Teraz każdy ważny stan jest jasno określony i wzajemnie wykluczający się.
Zapytanie może mieć tylko jeden z następujących stanów:
idle
loading
success
error
Nie ma możliwości przypadkowego przedstawienia jednocześnie stanów „ładowanie” i „sukces”.
Renderyzacja staje się również znacznie prostsza do zrozumienia:
if (status === 'loading') {
return <Spinner />;
}
if (status === 'error') {
return <ErrorMessage />;
}if (status === 'success') {
return <SuccessBanner />;
}return <DefaultForm />;
To nie jest tylko dodatkową zaletą TypeScript.
Odbija to szerszą zasadę projektowania:
Gdy dwa elementy stanu nigdy nie mogą być prawdziwe niezależnie od siebie, nie powinny być modelowane jako dwie oddzielne zmienne.
5. Unikaj używania efektów do synchronizacji stanu
To kolejny przypadek, gdy dodatkowe zmienne stanu generują zbędny, unikniony kod.
Weźmy taką konfigurację:
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');
W połączeniu z:
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
To, co tutaj stworzyłeś, to mechanizm synchronizacji wartości, która od początku nie potrzebowała własnego stanu.
Prostsze rozwiązanie:
const fullName = `${firstName} ${lastName}`;
Zmiana może wydawać się drobna, ale jej wpływ na architekturę jest znaczący.
Oryginalna wersja wymaga:
- Trzech oddzielnych zmiennych stanu
Zrewidowana wersja wymaga jedynie:
- Dwóch rzeczywistych wartości
- Jednej wartości wyprowadzonej z nich
- Żadnego efektu
- Żadnej logiki synchronizacji do utrzymania
Dobrym nawykiem podczas przeglądania komponentów React jest zadawanie pytania:
"Dlaczego ta wartość musi znajdować się poza obliczeniami renderowania?"
Jeśli uczciwa odpowiedź brzmi „aby być zsynchronizowana z czymś innym”, to zazwyczaj oznacza, że ta wartość w ogóle nie powinna być stanem.
6. Trzy pytania do zadań przed dodaniem stanu
Zanim napiszesz tę linię:
const [value, setValue] = useState(...);
najpierw sprawdź trzy kryteria.
Pytanie 1: Czy można to obliczyć podczas renderowania?
Jeśli dane potrzebne do utworzenia tej wartości już istnieją, nie ma powodu, by przechowywać jej kopię.
Zamiast:
const [itemCount, setItemCount] = useState(0);
pisać:
const itemCount = items.length;
Zamiast:
const [hasErrors, setHasErrors] = useState(false);
pisać:
const hasErrors = fields.some(field => field.error);
Unikaj przechowywania stanu dla wszystkiego, co można wyliczyć na miejscu.
Pytanie 2: Czy powinno to przetrwać ponowne załadowanie strony?
Jeśli tak, rozważ, czy URL jest odpowiednim nośnikiem tych danych.
Typowe przykłady:
- Słowa kluczowe
- Stan paginacji
- Aktywne filtry
- Kolejność sortowania
- Wybrane karty
- Odległości czasowe
Zadaj sobie pytanie: jeśli zostanie udostępniony link do tej strony, czy odbiorca powinien trafić dokładnie na tę samą widok?
Jeśli tak, URL zazwyczaj jest lepszym miejscem przechowywania tych danych.
Pytanie 3: Czy przeglądarka może przechowywać to dane do momentu wysłania formularza?
Jeśli wartość pola ma znaczenie tylko w momencie wysyłki, może wystarczyć niekontrolowany element wejścia.
Zamiast:
const [email, setEmail] = useState('');
może wystarczyć po prostu:
<input name="email" type="email" />
i odczytać ją później za pomocą:
const formData = new FormData(e.currentTarget);
Pozwól przeglądarce zarządzać tym, co już potrafi obsłużyć.
7. Co naprawdę należy do stanu w React
Żadne z tego nie jest argumentem za całkowitym usunięciem stanu komponentów.
Próba jego całkowitego usunięcia byłaby nadmierną korektą.
Bardzo wiele wartości rzeczywiście nadaje się do przechowywania w stanie.
Naprzykład:
const [isOpen, setIsOpen] = useState(false);
Tymczasowy menu rozwijane lub okno modalne wcale nie musi być odzwierciedlone w adreście URL.
Interaktywny stan
const [activeStep, setActiveStep] = useState(1);
Gdy wartość steruje interakcją, która odbywa się wyłącznie w obrębie jednego komponentu, przechowywanie jej w lokalnym stanie jest często właściwym rozwiązaniem.
Wartości, które zmieniają się niezależnie
Jeśli coś rzeczywiście ma swój własny cykl życia i nie ma sposobu, by obliczyć go na podstawie już posiadanych danych, wtedy stan jest odpowiednim miejscem dla niego.
Optymistyczne interfejsy użytkownika
Czasami trzeba pokazać coś na ekranie, zanim serwer faktycznie potwierdzi, że to się wydarzyło.
To również jest uzasadnionym zastosowaniem stanu.
Cel nie polega na przestrzeganiu tej zasady:
"Używaj mniej
useState, bez względu na wszystko."
Zamiast tego cel jest taki:
"Upewnij się, że każdy element stanu ma odpowiedniego właściciela."
Czasami odpowiednim właścicielem jest React.
Czasami to URL.
Czasami to DOM.
Czasami to serwer.
Czasami to dedykowana warstwa pobierania danych lub zarządzania stanem.
To właśnie decyzja dotycząca własności naprawdę kształtuje architekturę aplikacji.
8. Skup się na tym, kto posiada stan, a nie tylko na tym, ile go masz
Dobrze zbudowana aplikacja React nie jest definiowana przez małą liczbę zmiennych stanu.
Najważniejsze jest przydzielenie każdemu elementowi stanu jasnego, właściwego właściciela.
Weźmy panel sterowania, który śledzi:
Search query
Filters
Pagination
Sort order
Form values
Server data
Loading status
Selected row
Modal visibility
Derived totals
Kuszące jest umieszczenie wszystkich tych elementów bez zastanowienia do useState.
Jednak te wartości nie pełnią tego samego zadania, więc nie powinny znajdować się w tym samym miejscu.
Bardziej przydatny model myślowy dzieli je w następujący sposób:
URL
- Szukanie
- Filtrzy
- Paginacja
- Kolejność sortowania
- Karty do udostępniania
DOM
- Wartości formularza, które mają znaczenie tylko po jego wysłaniu
Server / warstwa danych
- Dane klientów
- Odpowiedzi API
- Dane serwera w pamięci cache’u
Wartości pochodne
- Liczby
- Sumy
- Listy filtrowane
- Podsumowania walidacji
Stan React
- Widoczność modalu
- Tymczasowe wybory
- Stan animacji
- Krótkotrwałe interakcje
Gdy w ten sposób rozdzielisz stan, komponent będzie miał o wiele mniej elementów do synchronizacji.
To właśnie dzięki tej redukcji pojawia się rzeczywista korzyść.
9. Podwójny stan to źródło błędów jutro
Najbardziej ryzykowny stan w aplikacji to niekoniecznie najskomplikowanszy.
Często jest to stan, który istnieje jednocześnie w więcej niż jednym miejscu.
Załóżmy, że masz:
const [products, setProducts] = useState([]);
const [filteredProducts, setFilteredProducts] = useState([]);
Teraz to ty jesteś odpowiedzialny za upewnienie się, że filteredProducts zawsze odpowiada zawartości products.
Dodając jeszcze jeden filtr, otrzymujesz:
const [filteredProducts, setFilteredProducts] = useState([]);
const [searchResults, setSearchResults] = useState([]);
Teraz pojawia się dodatkowa zależność, którą musisz ręcznie utrzymywać w spójności.
Ranо czy późno ktoś zaktualizuje podstawowe dane, ale zapomni zaktualizować jedną z zmiennych pochodnych, które od nich zależą.
Interfejs po cichu traci synchronizację z rzeczywistością.
Klient w końcu zgłasza coś takiego:
">Liczba wskazuje 42 produkty, ale na ekranie pokazuje się tylko 39."
To niezgodność zazwyczaj nie jest błędem samej logiki filtrowania.
Jest to objaw tego, że aplikacja ma więcej niż jedno źródło prawdy dla tych samych informacji.
Jeśli zamiast tego wyznaczysz tę wartość:
const filteredProducts = products.filter(...);
Znika cała klasa błędów, zanim zdążą wystąpić.
Dlatego redukcja stanu nie dotyczy wyłącznie wydajności renderowania.
Chodzi tu przede wszystkim o zmniejszenie liczby elementów, które mogą stracić synchronizację.
10. Koszt stanu wykracza poza ponowne renderowanie
Gdy ludzie rozmawiają o stanie, rozmowa zwykle koncentruje się na wydajności: „Czy to spowoduje kolejne renderowanie?”
To uzasadniona obawa, ale nie jest to najdroższy element całej sytuacji.
Prawdziwym kosztem jest złożoność.
Każda dodatkowa zmienna stanu to kolejna rzecz, którą zespół musi mieć na uwadze.
Następnie nieuchronnie dodaje się wywołanie funkcji setter.
setSomething(...)
Następnie, prawdopodobnie, useEffect, aby zareagować na to.
useEffect(...)
Potem tablica zależności wymieniająca wartości, które są monitorowane.
[firstValue, secondValue]
Następnie jakaś logika służąca do przywrócenia wartości do stanu początkowego.
setSomething(initialValue)
Następnie przypadek krawędziowy, w którym ten stan musi pozostać zgodny ze zmianą nawigacji.
Następnie zgłoszenie błędu, ponieważ stan nie został wyzerowany po pewnej modyfikacji.
Krok po kroku baza kodu przekształca się w sieć logiki synchronizacji.
To znacznie trudniejsze do zrozumienia niż w przypadku komponentu, którego dane pochodzą w sposób uporządkowany z kilku dobrze zdefiniowanych źródeł.
Prawdziwa umiejętność w React nie polega na zarządzaniu większą ilością stanu
Polega na zrozumieniu gdzie każdy element stanu powinien się znajdować.
Mniej doświadczony programista patrzący na interfejs użytkownika może zapytać:
">Który stan jest tu potrzebny?"
Lepszy instynkt architektoniczny prowadzi do innego pytania:
„Które z tych wartości naprawdę muszą być przechowywane w stanie aplikacji?”
A najbardziej precyzyjna wersja tego pytania brzmi:
„Kto jest prawowitym właścicielem każdej części stanu aplikacji?”
Zmiana sposobu myślenia w ten sposób może całkowicie odmienić podejście do tworzenia aplikacji React.
Adres URL może być odpowiedzialny za stan nawigacji.
Brauzer może zarządzać stanem formularzy.
Serwer może przechowywać dane należące do niego.
Wartości pochodne mogą pozostać takie, jakie są, zamiast być kopiowane.
Unie w TypeScript mogą modelować stany, które są wzajemnie wykluczające się.
A sam React może skupić się na krótkotrwałym, interaktywnym stanie, który faktycznie należy do warstwy interfejsu użytkownika.
Taka podział pracy prowadzi do znacznie bardziej uporządkowanej architektury.
Podsumowanie
Mieć więcej stanów nie jest samym w sobie osiągnięciem.
Zazwyczaj oznacza to jedynie większą ilość pracy związanej ze synchronizacją, więcej zależności do śledzenia oraz większe szanse na niezgodność danych.
Zanim użyjesz kolejnego useState, przeanalizuj następujące pytania:
- Czy tę wartość można obliczyć podczas renderowania? Jeśli tak, wyprowadź ją zamiast przechowywać osobno.
- Czy musi przetrwać odświeżenie strony lub być udostępnialna za pomocą linku? Jeśli tak, umieść ją w parametrach wyszukiwania URL.
- Czy to pole formularza musi być śledzone przy każdym naciśnięciu klawisza? Jeśli nie, kontrolowany input w połączeniu z
FormDatamoże wystarczyć.
Celem nigdy nie było minimalizowanie liczby surowych wywołań useState.
Celem jest upewnienie się, że jako najmniej wartości odpowiada za określanie prawdy w twojej aplikacji.
Ponieważ każdy niepotrzebny element stanu to kolejna rzecz, którą aplikacja musi synchronizować.
A każdy krok synchronizacji, którego unikniesz, to błąd, którego nigdy nie będziesz musiał naprawiać.