Naucz się bezpiecznie refaktoryzować komponenty React z pomocą Claude Code: characterization tests, małe kroki i rozplątywanie stanu, by poprawić strukturę bez zmiany zachowania.

Refaktoryzacje w React wyglądają ryzykownie, bo większość komponentów to nie małe, czyste bloczki, a żywe zlepki UI, stanu, efektów i "jeszcze jednego prop-a". Gdy zmieniasz strukturę, często niezamierzenie zmieniasz timing, tożsamość lub przepływ danych.
Refaktor zwykle zmienia zachowanie, gdy przypadkowo:
Refaktory często zamieniają się w przepisywanie, gdy „sprzątanie” miesza się z „ulepszaniem”. Zaczynasz od wyodrębnienia komponentu, potem zmieniasz nazwy, potem „naprawiasz” kształt stanu, potem wymieniasz hook. Wkrótce zmieniasz logikę i layout jednocześnie. Bez zabezpieczeń trudno ustalić, która zmiana spowodowała błąd.
Bezpieczny refaktor ma jedną prostą obietnicę: użytkownicy otrzymują to samo zachowanie, a ty kończysz z czytelniejszym kodem. Props, zdarzenia, stany ładowania, stany błędów i przypadki brzegowe powinny działać tak samo. Jeśli zachowanie się zmienia, powinno to być celowe, małe i wyraźnie oznaczone.
Jeśli refaktoryzujesz komponenty React z Claude Code (lub dowolnym asystentem kodowania), traktuj go jak szybkiego pair programmera, nie autopilota. Poproś, by opisał ryzyka przed zmianami, zaproponował plan z małymi krokami i wyjaśnił, jak sprawdził, że zachowanie pozostało takie samo. Potem zweryfikuj samodzielnie: uruchom aplikację, kliknij dziwne ścieżki i oprzyj się na testach, które utrwalają to, co komponent robi dziś, a nie na tym, co byś chciał, żeby robił.
Wybierz jeden komponent, który aktywnie zabiera ci czas. Nie całą stronę, nie „warstwę UI” i nie mglisty „cleanup”. Wybierz pojedynczy komponent, który jest trudny do czytania, trudny do zmiany lub pełen kruchego stanu i efektów. Wąski cel ułatwia też weryfikację sugestii asystenta.
Napisz cel, który możesz sprawdzić w pięć minut. Dobre cele dotyczą struktury, nie efektów: „podziel na mniejsze komponenty”, „upewnij stan jest łatwiejszy do śledzenia” albo „zrób testowalnym bez mockowania połowy aplikacji”. Unikaj celów typu „poprawić” albo „zwiększyć wydajność” chyba że masz metrykę i znane wąskie gardło.
Ustal granice zanim otworzysz edytor. Najbezpieczniejsze refaktory są nudne:
Następnie wypisz zależności, które potrafią po cichu złamać zachowanie podczas przesuwania kodu: wywołania API, providery kontekstu, parametry routingu, feature flagi, zdarzenia analityczne i współdzielony globalny stan.
Konkretny przykład: masz 600-wierszowy OrdersTable, który pobiera dane, filtruje je, zarządza selekcją i pokazuje drawer z detalami. Jasny cel może brzmieć: „wyodrębnij renderowanie wiersza i UI drawer-a do komponentów oraz przenieś stan selekcji do jednego reducera, bez zmian UI.” Ten cel mówi, co znaczy „gotowe” i co jest poza zakresem.
Zanim zaczniesz refaktor, traktuj komponent jak czarną skrzynkę. Twoim zadaniem jest utrwalić to, co robi dziś, nie to, co byś chciał, żeby robił. To powstrzymuje refaktor przed przemianą w redesign.
Zacznij od spisania obecnego zachowania prostym językiem: przy tych wejściach UI pokazuje ten output. Uwzględnij propsy, parametry URL, feature flagi i wszelkie dane z contextu lub store'a. Jeśli używasz Claude Code, wklej mały, skupiony fragment i poproś, by sparafrazował zachowanie w precyzyjnych zdaniach, które możesz później sprawdzić.
Pokryj stany UI, które ludzie faktycznie widzą. Komponent może wyglądać dobrze na happy path, a psuć się przy loadingu, pustym wyniku lub błędzie.
Zapisz też reguły implicytne, które łatwo przeoczyć i które często powodują, że refaktory łamią zachowanie:
Przykład: masz tabelę użytkowników, która ładuje wyniki, wspiera wyszukiwanie i sortuje po „Last active”. Zapisz, co się dzieje, gdy wyszukiwanie jest puste, gdy API zwraca pustą listę, gdy API zgłasza błąd i gdy dwóch użytkowników ma ten sam czas „Last active”. Zwróć uwagę na szczegóły jak case-insensitive sortowanie i czy tabela zachowuje bieżącą stronę przy zmianie filtra.
Gdy twoje notatki będą nudne i szczegółowe, jesteś gotów.
Characterization tests to testy opisujące „tak to dziś działa”. Mówią, jakie jest obecne zachowanie, nawet jeśli jest dziwne lub nieidealne. Brzmi to odwrotnie, ale chroni refaktor przed cichym przemienieniem się w rewrite.
Gdy refaktoryzujesz komponenty React z Claude Code, te testy są twoimi szynami bezpieczeństwa. Narzędzie może pomagać przekształcać kod, ale ty decydujesz, co nie może się zmienić.
Skup się na tym, na czym polegają użytkownicy (i inny kod):
Aby utrzymać stabilność testów, asercje opisują rezultaty, nie implementację. Lepiej sprawdzić „przycisk Zapisz jest wyłączony i pojawia się komunikat” niż „setState został wywołany” czy „hook się uruchomił”. Jeśli test pęka dlatego, że zmieniłeś nazwę komponentu lub przeorderowałeś hooki, to nie chronił zachowania.
Asynchroniczne zachowanie to miejsce, gdzie refaktory często zmieniają timing. Traktuj to jawnie: poczekaj, aż UI się ustabilizuje, potem asserty. Jeśli są timery (debounced search, opóźnione toasty), użyj fake timerów i przesuwaj czas. Jeśli są wywołania sieci, mockuj fetch i sprawdzaj, co widzi użytkownik po sukcesie i po błędzie. Dla flow podobnych do Suspense testuj zarówno fallback, jak i widok po załadowaniu.
Przykład: tabela „Users” pokazuje „No results” dopiero po zakończeniu wyszukiwania. Characterization test powinien utrwalić tę sekwencję: najpierw loading, potem albo wiersze, albo komunikat pustki, niezależnie od tego, jak później podzielisz komponent.
Sukces to nie „większe zmiany szybciej”. To jasny obraz tego, co komponent robi, a potem zmiana jednej małej rzeczy na raz przy zachowaniu stabilności zachowania.
Zacznij od wklejenia komponentu i poproś o opis jego odpowiedzialności w prostym języku. Dopytuj: jakie dane pokazuje, jakie akcje użytkownika obsługuje i jakie efekty uboczne wywołuje (fetching, timery, subskrypcje, analytics). To często ujawnia ukryte zadania, które czynią refaktory ryzykownymi.
Następnie poproś o mapę zależności. Chcesz inwentarz każdego inputu i outputu: propsy, odczyty contextu, custom hooki, lokalny stan, wartości pochodne, efekty i pomocniki na poziomie modułu. Przydatna mapa wskaże też, co jest bezpieczne do przeniesienia (czyste kalkulacje), a co jest „lepkie” (timing, DOM, sieć).
Potem poproś o propozycje kandydatów do wyodrębnienia, z jedną surową regułą: oddziel części widokowe od części kontrolera ze stanem. Sekcje z dużą ilością JSX, które potrzebują tylko propsów, są świetnymi kandydatami. Sekcje mieszające handlery, asynchroniczność i aktualizacje stanu zwykle nie są.
Workflow, który trzyma się w realnym kodzie:
Checkpointy mają znaczenie. Poproś Claude Code o minimalny plan, gdzie każdy krok można zatwierdzić i odwrócić. Praktyczny checkpoint to np.: „Wyodrębnij <TableHeader> bez zmian logiki” przed dotykaniem sortowania.
Konkretny przykład: jeśli komponent renderuje tabelę klientów, kontrolki filtrów i pobiera dane, najpierw wyodrębnij markup tabeli (nagłówki, wiersze, widok pusty) do czystego komponentu. Dopiero potem przenoś stan filtrów lub efekt fetchu. Taka kolejność zatrzymuje błędy w JSX.
Przy dzieleniu dużego komponentu ryzyko to nie sama zmiana JSX, a niezamierzone zmiany w przepływie danych, timing lub wiązaniu zdarzeń. Traktuj wyodrębnianie jako ćwiczenie copy-and-wire, a dopiero potem porządkuj.
Zacznij od znalezienia granic, które już istnieją w UI, nie w strukturze pliku. Szukaj części, które możesz opisać jako „coś” w jednym zdaniu: nagłówek z akcjami, pasek filtrów, lista wyników, stopka z paginacją.
Bezpieczny pierwszy ruch to wyodrębnienie prezentacyjnych komponentów: propsy in, JSX out. Celowo zachowaj je nudnymi. Zero nowego stanu, zero efektów, zero nowych wywołań API. Jeśli oryginalny komponent miał handler kliknięcia robiący trzy rzeczy, trzymaj ten handler w rodzicu i przekaż go w dół.
Zwykle bezpieczne granice to: obszar nagłówka, lista i element wiersza, filtry (tylko inputy), kontrolki stopki (paginacja, sumy, bulk actions) oraz dialogi (otwieranie/zamykanie z callbackami przekazanymi w propsach).
Nazewnictwo ma większe znaczenie, niż myślisz. Wybieraj konkretne nazwy jak UsersTableHeader czy InvoiceRowActions. Unikaj zbiorczych nazw typu „Utils” czy „HelperComponent”, bo ukrywają odpowiedzialności i zachęcają do mieszania obaw.
Wprowadź kontener komponent tylko wtedy, gdy jest realna potrzeba: część UI musi mieć własny stan lub efekty, aby pozostać spójna. Nawet wtedy trzymaj go wąskim. Dobry kontener ma jedną odpowiedzialność (np. „stan filtrów”) i przekazuje resztę przez propsy.
Bałagan w komponentach zwykle wynika z mieszania trzech rodzajów danych: realny stan UI (co użytkownik zmienił), dane pochodne (co można obliczyć) i stan serwera (co przychodzi z sieci). Jeśli traktujesz to wszystko jako lokalny stan, refaktory stają się ryzykowne, bo łatwo zmienisz moment aktualizacji.
Zacznij od oznaczenia każdej części danych. Zapytaj: czy użytkownik to edytuje, czy mogę to obliczyć z propsów, stanu i pobranych danych? I: czy ta wartość jest tu własniona, czy tylko przekazywana?
Wartości pochodne nie powinny żyć w useState. Przenieś je do małej funkcji albo zmemoizowanego selektora, jeśli są kosztowne. To zmniejsza aktualizacje stanu i sprawia, że zachowanie jest przewidywalniejsze.
Bezpieczny wzorzec:
useState tylko wartości edytowane przez użytkownika.useMemo.Efekty psują zachowanie, gdy robią za dużo albo reagują na złe zależności. Celuj w jeden efekt na jeden cel: jeden do sync z localStorage, jeden do fetchowania, jeden do subskrypcji. Jeśli efekt czyta wiele wartości, zwykle ukrywa dodatkowe odpowiedzialności.
Jeśli używasz Claude Code, poproś o drobną zmianę: rozdzielenie jednego efektu na dwa albo przeniesienie jednej odpowiedzialności do helpera. Potem uruchom characterization tests po każdym ruchu.
Uważaj na prop drilling. Zastąpienie go contextem pomaga tylko wtedy, gdy usuwa powtarzające się wiązanie i wyjaśnia właścicielstwo. Dobry znak: context czyta się jak pojęcie aplikacyjne (current user, theme, feature flags), a nie obejście dla jednego drzewa komponentów.
Przykład: komponent tabeli może trzymać rows i filteredRows w stanie. Trzymaj rows w stanie, oblicz filteredRows z rows i query, a filtrując trzymaj logikę w czystej funkcji, łatwej do testowania i trudnej do złamania.
Refaktory idą źle najczęściej, gdy zmieniasz za dużo zanim to zauważysz. Rozwiązanie jest proste: pracuj w malutkich checkpointach i traktuj każdy jak mini-wydanie. Nawet jeśli pracujesz na jednej gałęzi, rób PR-y w rozmiarze, który pozwoli zrozumieć, co się zepsuło i dlaczego.
Po każdym znaczącym ruchu (wyodrębnienie komponentu, zmiana przepływu stanu) zatrzymaj się i udowodnij, że nie zmieniłeś zachowania. Dowód to testy (zautomatyzowane) i szybki check w przeglądarce. Celem nie jest perfekcja, a szybkie wykrycie regresji.
Praktyczna pętla checkpoint:
Jeśli używasz platformy takiej jak Koder.ai, migawki i rollback mogą być jak szyny bezpieczeństwa podczas iteracji. Nadal chcesz normalnych commitów, ale migawki pomagają porównać „znaną dobrą” wersję z obecną lub gdy eksperyment idzie nie tak.
Trzymaj prosty ledger zachowania podczas pracy. To krótka notatka, co zweryfikowałeś — zapobiega powtarzaniu tych samych sprawdzeń.
Na przykład:
Gdy coś się zepsuje, ledger mówi, co trzeba ponownie sprawdzić, a checkpointy czynią cofanie tanim.
Większość refaktorów psuje się w małych, nudnych miejscach. UI nadal działa, ale znika reguła odstępów, handler klika dwa razy lub lista traci fokus podczas pisania. Asystenci mogą to pogorszyć, bo kod wygląda czyściej, nawet gdy zachowanie dryfuje.
Jedna częsta przyczyna to zmiana struktury. Wyodrębniasz komponent i owijasz go dodatkowym \u003cdiv\u003e, albo zamieniasz \u003cbutton\u003e na klikalny \u003cdiv\u003e. Selektory CSS, układ, nawigacja klawiaturą i testy mogą się zmienić, a nikt tego nie zauważy.
Pułapki, które najczęściej łamią zachowanie:
{} lub () => {}) może wywołać dodatkowe rendery i reset child state. Pilnuj propsów, które kiedyś były stabilne.useEffect, useMemo lub useCallback może wprowadzić przestarzałe wartości lub pętle, jeśli zależności się zmienią. Jeśli efekt kiedyś uruchamiał się „on click”, nie zamieniaj go na coś, co uruchamia się „gdy cokolwiek się zmieni”.Konkretny przykład: podział komponentu tabeli i zmiana kluczy wierszy z ID na indeks tablicy może wyglądać poprawnie, ale zepsuje zaznaczanie, gdy wiersze się przestawią. Traktuj „czystość” jako bonus. „To samo zachowanie” jako wymóg.
Przed mergem chcesz dowód, że refaktor zachował zachowanie. Najłatwiejszym sygnałem jest nudne: wszystko nadal działa bez konieczności „naprawiania” testów.
Szybkie przejście po ostatniej zmianie:
onChange nadal fire'uje na input użytkownika, nie na mount).Szybkie sanity check: otwórz komponent i przejdź jedną dziwną ścieżkę, np. wywołaj błąd, spróbuj ponownie, potem wyczyść filtry. Refaktory często psują przejścia, nawet jeśli główna ścieżka działa.
Jeśli coś nie działa, cofnij ostatnią zmianę i zrób ją ponownie w mniejszym kroku. To zwykle szybsze niż debugowanie dużego diffu.
Wyobraź sobie ProductTable, który robi wszystko: pobiera dane, zarządza filtrami, kontroluje paginację, otwiera confirm dialog dla usunięcia i obsługuje akcje wierszy jak edycja, duplikacja i archiwizacja. Zaczynał mały, a urósł do 900 wierszy.
Objawy są znane: stan rozrzucony po wielokrotnych useState, kilka useEffect uruchamia się w konkretnej kolejności, a jedna „nieszkodliwa” zmiana łamie paginację tylko przy aktywnym filtrze. Ludzie przestają go dotykać, bo jest nieprzewidywalny.
Zanim zmienisz strukturę, zablokuj zachowanie kilkoma characterization tests. Skup się na tym, co robi użytkownik, nie na stanie wewnętrznym:
Teraz możesz refaktoryzować w małych commitach. Plan wyodrębnienia może wyglądać tak: FilterBar renderuje kontrolki i emituje zmiany filtrów; TableView renderuje wiersze i paginację; RowActions zawiera menu akcji i confirm dialog; a hook useProductTable trzyma brudną logikę (query params, wartości pochodne i efekty).
Kolejność ma znaczenie. Najpierw wyodrębnij głupi UI (TableView, FilterBar) przekazując propsy bez zmian. Najtrudniejsze zostaw na koniec: przenoszenie stanu i efektów do useProductTable. Przy tym trzymaj stare nazwy propsów i kształty eventów, żeby testy pozostały zielone. Jeśli test pęknie, znalazłeś zmianę zachowania, a nie problem ze stylem.
Jeśli chcesz, by refaktoryzowanie komponentów React z Claude Code było bezpieczne za każdym razem, zamień to, co zrobiłeś, w mały szablon do ponownego użycia. Cel to mniej niespodzianek, nie więcej procesu.
Zapisz krótki playbook, którego możesz się trzymać przy każdym komponencie, nawet gdy jesteś zmęczony:
Przechowaj to jako snippet w notatkach lub repo, żeby następny refaktor zaczynał się z tymi samymi zabezpieczeniami.
Gdy komponent jest stabilny i czytelny, wybierz następny krok według wpływu na użytkownika. Typowa kolejność: accessibility najpierw (labelki, fokus, klawiatura), potem wydajność (memoizacja, kosztowne rendery), potem sprzątanie (typy, nazwy, martwy kod). Nie mieszaj tych trzech w jednym PR.
Jeśli używasz workflowu vibe-coding jak Koder.ai (koder.ai), tryb planowania pomoże zapisać kroki przed dotknięciem kodu, a migawki i rollback posłużą jako checkpointy podczas iteracji. Po zakończeniu eksport źródła ułatwia przejrzenie finalnego diffu i zachowanie czystej historii.
Przestań refaktoryzować, gdy testy pokrywają zachowania, których boisz się złamać, następna zmiana to nowa funkcja, lub czujesz pokusę „ulepszyć” wszystko naraz. Jeśli rozdzielenie dużego formularza usunęło splątany stan, a testy pokrywają walidację i submit — wypuść zmiany. Pozostałe pomysły zapisz jako krótki backlog.
Reactowe refaktory często zmieniają tożsamość i czasowanie bez wyraźnego powodu. Typowe problemy to:
key.Zakładaj, że zmiana struktury może zmienić zachowanie, dopóki testy tego nie wykluczą.
Użyj wąskiego, łatwego do sprawdzenia celu skoncentrowanego na strukturze, nie na ogólnych „ulepszeniach”. Dobry cel wygląda tak:
Unikaj niejasnych celów typu „poprawić” chyba że masz konkretny metric i znany wąski gardło.
Traktuj komponent jak czarną skrzynkę i zapisz, co użytkownicy obserwują:
Jeśli twoje notatki brzmią nudno i szczegółowo, są przydatne.
Dodaj charakterystyczne testy (characterization tests), które opisują, co komponent robi teraz, nawet jeśli to dziwne.
Praktyczne cele:
Asserty w testach powinny sprawdzać wynik w UI, nie wewnętrzne wywołania hooków.
Poproś asystenta, aby zachowywał się jak uważny pair programmer:
Nie akceptuj dużego „rewrite” diffu; upieraj się przy stopniowych zmianach, które możesz zweryfikować.
Zacznij od wyodrębniania czystych prezentacyjnych części:
Najpierw copy-and-wire; porządkuj później. Po bezpiecznym rozdzieleniu UI zajmij się stanem/efektami małymi krokami.
Używaj stabilnych kluczy związanych z prawdziwą tożsamością (np. ID), a nie indeksów tablicy.
Klucze oparte na indeksach często „działają” dopóki nie sortujesz, filtrujesz, nie wstawiasz lub nie usuwasz wierszy — wtedy React może ponownie użyć złych instancji i zobaczysz błędy jak:
Jeśli refaktor zmienia klucze, traktuj to jako wysokie ryzyko i testuj przypadki przebudowy listy.
Trzymaj wartości pochodne poza useState, jeśli możesz je obliczyć z istniejących wejść.
Bezpieczne podejście:
Używaj punktów kontrolnych, żeby każdy krok można było szybko cofnąć:
Jeśli pracujesz w Koder.ai, migawki i rollback mogą uzupełniać normalne commity, gdy eksperyment wymyka się spod kontroli.
Przestań, gdy zachowanie jest zablokowane, a kod jest wyraźnie łatwiejszy do zmiany. Dobre sygnały do zatrzymania:
Wdróż refaktor, a pozostałe pomysły (accessibility, performance, cleanup) odłóż jako osobne zadania.
filteredRowsrowsqueryuseMemo tylko gdy obliczenia są kosztowneTo zmniejsza nieprzewidywalność aktualizacji i ułatwia rozumienie komponentu.