Modele mentalne React upraszczają React: poznaj kluczowe idee dotyczące komponentów, renderowania, stanu i efektów, a potem wykorzystaj je do szybkiego budowania UI przez czat.

React może być frustrujący na początku, bo widzisz zmianę UI, ale nie zawsze potrafisz wyjaśnić, dlaczego do niej doszło. Klikasz przycisk, coś się aktualizuje, a inna część strony cię zaskakuje. To zwykle nie jest „React jest dziwny”. To „moje wyobrażenie o tym, co robi React, jest nieostre”.
Model mentalny to prosta opowieść, którą sobie opowiadasz o tym, jak coś działa. Jeśli opowieść jest błędna, podejmujesz pewne decyzje, które prowadzą do mylących rezultatów. Pomyśl o termostacie: zły model to „ustawiam 22°C, więc pokój od razu robi się 22°C”. Lepszy model to „ustawiam cel, a grzejnik włącza się i wyłącza w czasie, żeby do niego dojść”. Z lepszą opowieścią zachowanie przestaje wydawać się losowe.
React działa tak samo. Gdy przyjmiesz kilka jasnych idei, React staje się przewidywalny: możesz spojrzeć na bieżące dane i wiarygodnie zgadnąć, co będzie na ekranie.
Dan Abramov pomógł spopularyzować podejście „uczynić to przewidywalnym”. Celem nie jest zapamiętanie reguł. Chodzi o to, żeby mieć w głowie mały zestaw prawd, dzięki którym będziesz debugować przez rozumowanie, a nie metodą prób i błędów.
Trzymaj te idee w pamięci:
Trzymaj się tego, a React przestaje wyglądać jak magia. Zaczyna być systemem, któremu można zaufać.
React staje się łatwiejszy, gdy przestaniesz myśleć w kategoriach „ekranów” i zaczniesz myśleć o małych częściach. Komponent to wielokrotnego użytku kawałek UI. Przyjmuje wejścia i zwraca opis, jak UI powinien wyglądać dla tych wejść.
Pomaga traktować komponent jako czysty opis: „dla tych danych pokaż to”. Taki opis można użyć w wielu miejscach, bo nie zależy od tego, gdzie komponent żyje.
Propsy to wejścia. Pochodzą od komponentu rodzica. Propsy nie są „własnością” komponentu i nie powinny być przez niego cicho zmieniane. Jeśli przycisk otrzyma label="Save", jego zadaniem jest wyrenderować tę etykietę, a nie decydować, że powinna być inna.
Stan to dane będące własnością. To to, co komponent pamięta w czasie. Stan zmienia się, gdy użytkownik wchodzi w interakcję, gdy kończy się żądanie, albo gdy zdecydujesz, że coś powinno być inne. W przeciwieństwie do propsów, stan należy do tego komponentu (albo do tego, który postanowisz, że go posiada).
Prosta wersja kluczowej idei: UI to funkcja stanu. Jeśli stan mówi „loading”, pokaż spinner. Jeśli stan mówi „error”, pokaż komunikat. Jeśli stan mówi „items = 3”, wyrenderuj trzy wiersze. Twoim zadaniem jest czytać UI ze stanu, a nie wpadać w ukryte zmienne.
Szybki sposób na rozdzielenie pojęć:
SearchBox, ProfileCard, CheckoutForm)name, price, disabled)isOpen, query, selectedId)Przykład: modal. Rodzic może przekazać title i onClose jako propsy. Modal może mieć isAnimating jako stan.
Nawet jeśli generujesz UI przez czat (na przykład w Koder.ai), takie rozdzielenie nadal jest najszybszym sposobem, by zachować zdrowy rozsądek: najpierw zdecyduj, co jest propsami, a co stanem, potem pozwól, żeby UI podążało za tym.
Użyteczny sposób myślenia o React (bardzo w duchu Dan Abramova) to: renderowanie to obliczenie, a nie malowanie. React uruchamia funkcje twoich komponentów, żeby dowiedzieć się, jak UI powinno wyglądać dla bieżących propsów i stanu. Wynik to opis UI, nie piksele.
Ponowne renderowanie oznacza tylko, że React powtarza to obliczenie. To nie znaczy „cała strona się przerysowuje”. React porównuje nowy wynik z poprzednim i stosuje najmniejszy zestaw zmian do prawdziwego DOM. Wiele komponentów może się przerysować, podczas gdy tylko kilka węzłów DOM faktycznie się zaktualizuje.
Większość rerenderów dzieje się z kilku prostych powodów: zmienił się stan komponentu, zmieniły się jego propsy, albo rodzic się przerysował i React poprosił dziecko o wyrenderowanie. Ten ostatni przypadek zaskakuje ludzi, ale zwykle jest w porządku. Jeśli traktujesz render jako „tani i nudny”, aplikacja staje się łatwiejsza do rozumienia.
Złota zasada, która to porządkuje: spraw, by render był czysty (pure). Dla tych samych wejść (props + state) komponent powinien zwrócić ten sam opis UI. Trzymaj niespodzianki poza renderem.
Konkret: jeśli generujesz ID przez Math.random() w renderze, przy rerenderze się ono zmieni i nagle checkbox straci fokus albo element listy zostanie zamontowany na nowo. Stwórz ID raz (stan, memo lub poza komponentem), a render stanie się stabilny.
Jeśli zapamiętasz jedno zdanie: rerender oznacza „przelicz, jak powinno wyglądać UI”, a nie „zbuduj wszystko od nowa”.
Kolejny pomocny model: aktualizacje stanu to żądania, a nie natychmiastowe przypisania. Kiedy wywołujesz setter jak setCount(count + 1), prosisz React o zaplanowanie renderu z nową wartością. Jeśli odczytasz stan zaraz potem, możesz wciąż zobaczyć starą wartość, bo React jeszcze nie wyrenderował.
Dlatego ważne są „małe i przewidywalne” aktualizacje. Preferuj opisywanie zmiany zamiast chwytania tego, co twoim zdaniem jest aktualne. Gdy następna wartość zależy od poprzedniej, użyj formy updater: setCount(c => c + 1). Pasuje to do modelu React: wiele aktualizacji może być w kolejce, a potem zastosowane w kolejności.
Niezmienność to druga połowa obrazu. Nie zmieniaj obiektów i tablic w miejscu. Stwórz nowy obiekt z wprowadzoną zmianą. React wtedy widzi „ta wartość jest nowa”, a twój mózg może śledzić, co się zmieniło.
Przykład: przełączanie todo. Bezpieczne podejście to stworzyć nową tablicę i nowy obiekt dla zmienionego zadania. Ryzykowne podejście to przełączenie todo.done = !todo.done w istniejącej tablicy.
Trzymaj też stan minimalny. Pułapką jest przechowywanie wartości, które możesz policzyć. Jeśli masz items i filter, nie przechowuj filteredItems w stanie. Oblicz to podczas renderu. Mniej zmiennych stanu to mniej sposobów na rozjazd wartości.
Prosty test, co powinno być w stanie:
Jeśli budujesz UI przez czat (w tym w Koder.ai), proś o zmiany jako małe poprawki: „Dodaj jedno pole boolean” albo „Zaktualizuj tę listę niemutowalnie”. Małe, jawne zmiany utrzymują generator i twój kod React w zgodzie.
Renderowanie opisuje UI. Efekty synchronizują ze światem zewnętrznym. „Na zewnątrz” oznacza rzeczy, nad którymi React nie ma kontroli: wywołania sieciowe, timery, API przeglądarki i czasem imperatywna praca z DOM.
Jeśli coś można obliczyć z propsów i stanu, zwykle nie powinno to żyć w efekcie. Umieszczenie tego w efekcie dodaje drugi krok (render, uruchom efekt, ustaw stan, render ponownie). Ten dodatkowy skok to miejsce, gdzie pojawiają się migotania, pętle i błędy „dlaczego to jest nieaktualne?”.
Częste nieporozumienie: masz firstName i lastName, i przechowujesz fullName w stanie za pomocą efektu. Ale fullName nie jest efektem ubocznym. To dane pochodne. Oblicz je podczas renderu i zawsze będzie zgodne.
Zwyczaj: wyprowadzaj wartości UI podczas renderu (lub użyj useMemo, gdy coś jest naprawdę kosztowne), a efekty używaj do „zrób coś” pracy, nie do „ustal czegoś”.
Traktuj tablicę zależności jako: „Gdy te wartości się zmienią, ponownie zsynchronizuj ze światem zewnętrznym.” To nie jest trik wydajnościowy i nie miejsce na uciszanie ostrzeżeń.
Przykład: jeśli pobierasz szczegóły użytkownika, gdy zmienia się userId, userId należy do tablicy zależności, bo powinien wywołać synchronizację. Jeśli efekt używa też token, uwzględnij go, inaczej możesz pobrać z przestarzałym tokenem.
Dobry test intuicyjny: jeśli usunięcie efektu spowoduje jedynie błędne UI, to prawdopodobnie nie był to prawdziwy efekt. Jeśli jego usunięcie zatrzyma timer, przerwie subskrypcję lub pominie fetch, to prawdopodobnie był.
Jednym z najbardziej użytecznych modeli mentalnych jest prostota: dane idą w dół drzewa, a akcje użytkownika idą w górę.
Rodzic przekazuje wartości dzieciom. Dzieci nie powinny potajemnie „posiadać” tej samej wartości w dwóch miejscach. Proszą o zmiany, wywołując funkcję, a rodzic decyduje, jaka będzie nowa wartość.
Kiedy dwie części UI muszą się zgadzać, wybierz jedno miejsce, w którym przechowujesz wartość, a potem przekazuj ją w dół. To jest „lifting state”. Może się wydawać dodatkowym okablowaniem, ale zapobiega gorszemu problemowi: dwóm stanom, które się rozjeżdżają i wymuszają hacki, żeby je trzymać w syncu.
Przykład: pole wyszukiwania i lista wyników. Jeśli input przechowuje własne query, a lista też swoje query, w końcu zobaczysz „input pokazuje X, ale lista używa Y”. Naprawa to trzymać query w rodzicu, przekazać je do obu i przekazać onChangeQuery(newValue) do inputu.
Podnoszenie stanu nie zawsze jest odpowiedzią. Jeśli wartość ma znaczenie tylko wewnątrz jednego komponentu, trzymaj ją tam. Trzymanie stanu blisko miejsca użycia zazwyczaj ułatwia czytanie kodu.
Praktyczna granica:
Jeżeli nie jesteś pewien, czy podnieść stan, poszukaj sygnałów: dwa komponenty pokazują tę samą wartość różnymi sposobami; akcja w jednym miejscu musi zaktualizować coś daleko; kopiujesz propsy do stanu „na zapas”; albo dodajesz efekty tylko po to, by utrzymać dwie wartości w syncu.
Ten model pomaga też przy budowaniu przez narzędzia czatowe jak Koder.ai: poproś o jednego właściciela dla każdego kawałka współdzielonego stanu, a potem generuj handlery, które płyną do góry.
Wybierz funkcję na tyle małą, żeby zmieścić ją w głowie. Dobry przykład to lista z wyszukiwaniem, w której kliknięcie elementu pokazuje szczegóły w modalu.
Zacznij od naszkicowania części UI i zdarzeń, które mogą wystąpić. Nie myśl jeszcze o kodzie. Pomyśl, co użytkownik może zrobić i co może zobaczyć: jest pole wyszukiwania, lista, wyróżnienie wybranego wiersza i modal. Zdarzenia to wpisywanie w wyszukiwarce, klikanie elementu, otwieranie modalu i zamykanie modalu.
Teraz „narysuj stan”. Wypisz wartości, które trzeba przechować, i zdecyduj, kto je posiada. Prosta zasada działa dobrze: najbliższy wspólny rodzic miejsc, które potrzebują wartości, powinien ją posiadać.
Dla tej funkcji stan może być maleńki: query (string), selectedId (id lub null) i isModalOpen (boolean). Lista czyta query i renderuje elementy. Modal czyta selectedId, żeby pokazać szczegóły. Jeśli zarówno lista, jak i modal potrzebują selectedId, trzymaj je w rodzicu, nie w obu.
Następnie oddziel dane pochodne od przechowywanych. Filtrowana lista to dane pochodne: filteredItems = items.filter(...). Nie trzymaj jej w stanie, bo zawsze można ją przeliczyć z items i query. Przechowywanie danych pochodnych prowadzi do rozjazdów wartości.
Dopiero potem zapytaj: czy potrzebujemy efektu? Jeśli items są już w pamięci, nie. Jeśli wpisanie zapytania ma wywołać fetch, to tak. Jeśli zamknięcie modalu ma coś zapisać, to tak. Efekty są do synchronizacji (fetch, save, subscribe), nie do podstawowego okablowania UI.
Na końcu przetestuj przepływ kilkoma krawędziami:
selectedId nadal jest ważne?Jeśli potrafisz na to odpowiedzieć na papierze, kod React zwykle jest prosty.
Większość nieporozumień z React nie dotyczy składni. Dzieje się tak, gdy kod przestaje pasować do prostej historii w twojej głowie.
Przechowywanie danych pochodnych. Trzymasz fullName w stanie, choć to tylko firstName + lastName. Działa, dopóki jedno pole się nie zmieni, a drugie tak i UI pokazuje nieaktualną wartość.
Pętle efektów. Efekt pobiera dane, ustawia stan, a lista zależności powoduje ponowne uruchomienie. Objaw: powtarzające się żądania, drżący UI lub stan, który nigdy się nie ustabilizuje.
Stare zamknięcia (stale closures). Handler używa starej wartości (np. nieaktualnego licznika lub filtru). Objaw: „kliknąłem, ale użyło wczorajszej wartości”.
Globalny stan wszędzie. Wrzucenie każdego szczegółu UI do globalnego store utrudnia zrozumienie, co jest czyje. Objaw: zmieniasz jedną rzecz, a trzy ekrany reagują w zaskakujący sposób.
Mutowanie zagnieżdżonych obiektów. Zmieniasz obiekt lub tablicę na miejscu i dziwisz się, dlaczego UI się nie zaktualizował. Objaw: „dane się zmieniły, ale nic się nie wyrenderowało”.
Konkret: panel „wyszukaj i sortuj” dla listy. Jeśli trzymasz filteredItems w stanie, może się ono rozjechać z items, gdy przyjdą nowe dane. Zamiast tego trzymaj wejścia (tekst wyszukiwania, wybór sortowania) i oblicz listę podczas renderu.
Z efektami trzymaj je do synchronizacji ze światem (fetch, subskrypcje, timery). Jeśli efekt robi podstawową pracę UI, często powinien być w renderze lub handlerze.
Generując lub edytując kod przez czat, te błędy pojawiają się szybciej, bo zmiany mogą przychodzić w dużych kawałkach. Dobry nawyk to formułować żądania przez pryzmat własności: „Jakie jest źródło prawdy dla tej wartości?” i „Czy możemy to policzyć zamiast przechowywać?”.
Gdy UI zaczyna być nieprzewidywalne, rzadko jest to „za dużo React”. Zwykle to za dużo stanu, w niewłaściwych miejscach, robiącego prace, których nie powinien.
Zanim dodasz kolejny useState, zatrzymaj się i zapytaj:
Mały przykład: pole wyszukiwania, dropdown filtra, lista. Jeśli trzymasz zarówno query, jak i filteredItems w stanie, masz dwa źródła prawdy. Zamiast tego trzymaj query i filter w stanie, a filteredItems wyprowadzaj podczas renderu z pełnej listy.
To ma znaczenie także przy szybkim budowaniu przez narzędzia czatowe. Szybkość jest super, ale pytaj: „Dodaliśmy stan, czy przypadkowo dodaliśmy wartość pochodną?” Jeśli to pochodna, usuń ten stan i oblicz ją.
Mały zespół buduje UI administracyjny: tabela zamówień, kilka filtrów i dialog do edycji zamówienia. Pierwsze polecenie brzmi „Dodaj filtry i popup edycji”. To wydaje się proste, ale często kończy się rozrzuconym stanem.
Uczyń to konkretnym, tłumacząc żądanie na stan i zdarzenia. Zamiast „filtry”, nazwij stan: query, status, dateRange. Zamiast „popup edycji”, nazwij zdarzenie: „użytkownik kliknął Edytuj w wierszu”. Potem zdecyduj, kto jest właścicielem każdego kawałka stanu (strona, tabela czy dialog) i co można wyprowadzić (np. filtrowana lista).
Przykładowe prośby, które utrzymają model:
OrdersPage, który posiada filters i selectedOrderId. OrdersTable jest kontrolowany przez filters i wywołuje onEdit(orderId).”visibleOrders z orders i filters. Nie trzymaj visibleOrders w stanie.”EditOrderDialog, który otrzymuje order i open. Po zapisaniu wywołaj onSave(updatedOrder) i zamknij.”filters z URL, nie do obliczania wierszy.”Po wygenerowaniu lub zaktualizowaniu UI przejrzyj zmiany prostym testem: każda wartość stanu ma jednego właściciela, wartości pochodne nie są przechowywane, efekty służą tylko synchronizacji ze światem zewnętrznym (URL, sieć, storage), a zdarzenia płyną w dół jako propsy i w górę jako callbacki.
Gdy stan jest przewidywalny, iteracja jest bezpieczna. Możesz zmienić układ tabeli, dodać filtr lub poprawić pola w dialogu bez zgadywania, który ukryty stan się zepsuje.
Szybkość jest użyteczna tylko wtedy, gdy aplikacja pozostaje łatwa do rozumienia. Najprostsza ochrona to traktować te modele mentalne jak listę kontrolną, którą stosujesz przed napisaniem (lub wygenerowaniem) UI.
Zacznij każdą funkcję tak samo: zapisz stan, którego potrzebujesz, zdarzenia, które mogą go zmienić, i kto go posiada. Jeśli nie potrafisz powiedzieć „Ten komponent posiada ten stan i te zdarzenia go aktualizują”, prawdopodobnie skończysz z rozproszonym stanem i zaskakującymi rerenderami.
Jeśli budujesz przez czat, zacznij od trybu planowania. Opisz komponenty, kształt stanu i przejścia w prostym języku przed poproszeniem o kod. Na przykład: „Panel filtrów aktualizuje stan query; lista wyników jest wyprowadzana z query; wybranie elementu ustawia selectedId; zamknięcie czyści go.” Kiedy to brzmi czytelnie, generowanie UI staje się krokiem mechanicznym.
Jeśli używasz Koder.ai (koder.ai) do generowania kodu React, warto wykonać szybką kontrolę: jeden jasny właściciel dla każdej wartości stanu, UI wyprowadzone ze stanu, efekty tylko do synchronizacji i brak duplikatów źródeł prawdy.
Następnie iteruj małymi krokami. Jeśli chcesz zmienić strukturę stanu (np. z kilku booleanów na pojedyncze pole status), zrób snapshot, eksperymentuj i wycofaj, jeśli model mentalny się pogorszy. Przy głębszym przeglądzie lub przekazaniu projektu, eksport kodu ułatwia odpowiedzenie na pytanie: czy kształt stanu nadal opowiada historię UI?
Dobrym punktem startowym jest: UI = f(state, props). Twoje komponenty nie „edytują DOM”; opisują, co powinno być widoczne na ekranie dla bieżących danych. Jeśli ekran jest niepoprawny, sprawdź stan/propsy, które go wygenerowały, a nie DOM.
Propsy to wejścia od rodzica; komponent traktuje je jak tylko do odczytu. State to pamięć należąca do komponentu (lub do wybranego właściciela). Jeśli wartość musi być współdzielona, podnieś ją wyżej i przekaż jako propsy.
Rerender oznacza, że React ponownie uruchamia funkcję komponentu, żeby obliczyć kolejną opis UI. Nie znaczy to automatycznie, że cała strona jest przerysowywana. React potem aktualizuje rzeczywisty DOM najmniejszym zestawem potrzebnych zmian.
Aktualizacje stanu są planowane, a nie przypisaniami natychmiastowymi. Jeśli następna wartość zależy od poprzedniej, użyj formy updater, żeby nie polegać na być może przestarzałej wartości:
setCount(c => c + 1)To działa poprawnie nawet, gdy wiele aktualizacji jest w kolejce.
Unikaj przechowywania wszystkiego, co możesz obliczyć z istniejących wejść. Przechowuj wejścia, resztę wyprowadzaj podczas renderu.
Przykłady:
items, filtervisibleItems = items.filter(...)To zapobiega rozjeżdżaniu się wartości.
Używaj efektów do synchronizacji z rzeczami poza Reactem: fetch, subskrypcje, timery, API przeglądarki czy imperative DOM. Nie używaj efektu tylko po to, by obliczyć wartości UI z stanu—oblicz je w renderze (lub useMemo, jeśli koszt obliczeniowy jest wysoki).
Traktuj tablicę zależności jako listę triggerów: „gdy te wartości się zmienią, ponownie zsynchronizuj”. Dołącz każdą reaktywną wartość, której efekt używa. Jeśli czegoś zabraknie, ryzykujesz starą wartość (np. przestarzałe userId lub token). Jeśli dodasz złe wartości, możesz stworzyć pętle—często znak, że praca powinna być w handlerach lub renderze.
Jeśli dwie części UI muszą się zgadzać, umieść stan w ich najbliższym wspólnym rodzicu, przekaż wartość w dół i callbacki w górę.
Szybki test: jeśli duplikujesz tę samą wartość w dwóch komponentach i piszesz efekty, żeby je „synchronizować”, prawdopodobnie ten stan powinien mieć jednego właściciela.
Zwykle dzieje się tak, gdy handler „złapie” starą wartość z poprzedniego renderu. Typowe naprawy:
setX(prev => ...)Jeśli klik używa „wczorajszej” wartości, podejrzewaj starą klauzulę (stale closure).
Zacznij od małego planu: komponenty, właściciele stanu i zdarzenia. Generuj kod jako małe poprawki (dodaj jedno pole stanu, dodaj jeden handler, wyprowadz jedną wartość) zamiast dużych przeróbek.
Jeśli używasz kreatora czatu jak Koder.ai, poproś o:
To utrzyma generowany kod zgodny z mentalnym modelem React.