Technikalia

Koniec z useEffect — 3 sytuacje, gdy go nie potrzebujesz

Autor Eric Kudler, Mentor Fullstack·14 sierpnia 2024·7 min czytania

Większość kursów w sieci uczy Reacta na skróty. useEffect stał się takim workiem na wszystko, co nie jest prostym HTML-em, co sprawia, że kod sypie się przy 12. kliknięciu i zjada procesor klienta. Pokażę Ci, jak wywalić zbędne efekty i sprawić, by Twoja aplikacja po prostu działała szybciej.

Obliczanie danych na podstawie propsów lub stanu

Często widzę ten błąd w projektach, które trafiają do mnie na review: ktoś ma listę 47 produktów i chce ją przefiltrować. Zamiast zrobić to bezpośrednio w ciele komponentu, tworzy nowy stan 'filteredItems' i aktualizuje go wewnątrz useEffect za każdym razem, gdy zmieni się tekst w wyszukiwarce. To czysta strata czasu i zasobów. React musi wtedy wyrenderować komponent dwa razy zamiast raz. Jeśli Twoje obliczenia nie zajmują więcej niż 3-4 milisekundy, rób to bezpośrednio podczas renderowania. Kod staje się krótszy o 7 linii i znikają problemy z synchronizacją stanu.

Zastanów się, czy naprawdę potrzebujesz osobnej zmiennej w useState dla czegoś, co można wyliczyć z tego, co już masz. W zeszłym tygodniu pomagałem Markowi uprościć formularz zamówienia, który miał 9 niepotrzebnych efektów. Po ich usunięciu kod skrócił się o 114 linii, a formularz przestał 'migać' przy wpisywaniu danych. Pamiętaj, że każdy useEffect to dodatkowa robota dla przeglądarki. Jeśli masz 12 takich miejsc w małej aplikacji, użytkownik na starszym telefonie na pewno to poczuje. Siedzimy nad tym kodem po to, żeby był lekki, a nie żeby wyglądał na skomplikowany.

Warto też wspomnieć o useMemo. Używaj go tylko wtedy, gdy faktycznie mielisz duże ilości danych, na przykład tablicę z 542 rekordami. Dla mniejszych rzeczy prosta stała wewnątrz komponentu wystarczy. Nie komplikujmy sobie życia na siłę. Żadnej magii, tylko logika — to zasada, która uratowała już niejedno portfolio przed odrzuceniem przez rekrutera.

Każdy zbędny useEffect to dodatkowa robota dla przeglądarki, która spowalnia Twoją aplikację o te kilka kluczowych milisekund.
Obliczanie danych na podstawie propsów lub stanu

Reagowanie na zdarzenia użytkownika

Największy grzech to używanie useEffect do obsługi kliknięć. Wyobraź sobie, że masz przycisk 'Zapisz'. Zamiast wysłać dane do bazy bezpośrednio w funkcji handleClick, ustawiasz stan 'isSaving' na true, a potem w efekcie sprawdzasz, czy 'isSaving' jest prawdziwe i dopiero wtedy odpalasz zapytanie. To jest kodowanie naokoło. Logicznym miejscem na akcję jest moment, w którym użytkownik coś robi. Efekty są do synchronizacji z systemami zewnętrznymi, a nie do reagowania na to, że ktoś kliknął w niebieski guzik na ekranie.

Przeanalizowaliśmy 31 projektów moich kursantów z ostatniego kwartału i w 19 z nich znaleźliśmy ten schemat. Naprawienie tego zajmuje zwykle 6 minut, a drastycznie poprawia czytelność. Gdy wrócisz do takiego kodu za 4 miesiące, od razu będziesz wiedzieć, co się dzieje. W handleClick widzisz czarno na białym: kliknięto, wyślij, powiadom. W przypadku useEffect musisz śledzić zmiany w 3 różnych miejscach komponentu, co jest męczące i prowadzi do błędów, których potem szukasz przez 2 godziny.

Bywa, że programiści boją się, że funkcja handleClick będzie zbyt długa. Wtedy lepiej wydzielić logikę do osobnego pliku lub hooka, ale nadal wywoływać ją bezpośrednio. Bez lania wody: jeśli coś dzieje się po kliknięciu, niech dzieje się w funkcji obsługującej to kliknięcie. To prosta zasada, która odróżnia juniora od kogoś, kto faktycznie rozumie, jak działa React i nie boi się pisać kodu, który jest po prostu jasny.

Logicznym miejscem na akcję jest moment, w którym użytkownik coś robi, a nie ukryty efekt śledzący zmianę stanu.
Reagowanie na zdarzenia użytkownika

Inicjalizacja aplikacji i dane zewnętrzne

Pobieranie danych to klasyka. Wszyscy uczą się pisać useEffect z pustą tablicą zależności. Ale co, jeśli komponent zostanie zamontowany dwa razy? W React 18 to standard w trybie deweloperskim. Nagle Twoje API dostaje 2 zapytania zamiast jednego. Jeśli masz 83 użytkowników jednocześnie, serwer może to odczuć. Zamiast walczyć z efektami, warto spojrzeć w stronę bibliotek takich jak React Query lub SWR. One rozwiązują problemy z cache'owaniem i ponownym pobieraniem danych za Ciebie. Ty skupiasz się na tym, co użytkownik widzi na ekranie.

Jeśli jednak musisz użyć useEffect, pamiętaj o funkcji czyszczącej (cleanup function). To te kilka linii kodu, o których 8 na 10 początkujących zapomina. Bez tego ryzykujesz wycieki pamięci i błędy typu 'race conditions', gdzie starsze zapytanie nadpisuje nowsze dane. Widziałem to w aplikacji do śledzenia wydatków, którą pisał jeden z moich podopiecznych. Saldo skakało jak szalone, bo dane z API przychodziły w złej kolejności. Dodanie prostego 'ignore' w efekcie naprawiło problem w 3 minuty.

Szczerze mówiąc, im mniej useEffect masz w kodzie, tym łatwiej go testować. Testowanie efektów to zawsze ból głowy, bo musisz symulować cykl życia komponentu. Czyste funkcje i proste handlery są o niebo lepsze. W Eric Kudler Fullstack Mentoring kładziemy na to duży nacisk. Twoje portfolio ma pokazywać, że umiesz pisać kod, który łatwo utrzymać, a nie taki, który wymaga doktoratu z Reacta, żeby zrozumieć, dlaczego jeden guzik nie działa.

Inicjalizacja aplikacji i dane zewnętrzne

Kiedy useEffect jest faktycznie potrzebny?

Nie zrozum mnie źle, useEffect to nie jest zło wcielone. Jest niezbędny, gdy musisz wyjść poza świat Reacta. Na przykład, gdy chcesz podpiąć zewnętrzną bibliotekę do map (jak Leaflet), ustawić timer za pomocą setInterval, albo nasłuchiwać na zdarzenia globalne typu 'scroll' czy 'resize'. Wtedy React musi wiedzieć, kiedy ten system zewnętrzny podpiąć, a kiedy go odłączyć, żeby nie zapchać przeglądarki. To są sytuacje, w których synchronizacja jest kluczowa i tam efekt czuje się najlepiej.

W jednym z naszych projektów — prostym edytorze zdjęć — użyliśmy useEffect do synchronizacji płótna Canvas z filtrami wybranymi przez użytkownika. Działało to świetnie, bo Canvas nie jest częścią drzewa DOM Reacta. Mieliśmy tam 4 konkretne zależności i wszystko śmigało bez zacięć. Ale nawet tam staraliśmy się ograniczyć liczbę tych wywołań. Zasada jest prosta: najpierw szukaj rozwiązania bez efektu. Jeśli po 14 minutach kombinowania widzisz, że się nie da — użyj go, ale zrób to porządnie.

Podsumowując, kod, który działa, to kod przewidywalny. Nadużywanie useEffect sprawia, że Twoja aplikacja staje się czarną skrzynką, w której nie wiadomo, co wywołuje co. Wywal te 3 zbędne efekty ze swojego obecnego projektu jeszcze dzisiaj. Zobaczysz, że debugowanie stanie się o wiele przyjemniejsze. A jeśli chcesz, żebyśmy razem prześwietlili Twój kod, po prostu daj znać. Naprawmy to razem, zanim Twój kod trafi do rekrutera.

Używaj useEffect tylko wtedy, gdy musisz wyjść poza świat Reacta i pogadać z systemem zewnętrznym.
Kiedy useEffect jest faktycznie potrzebny?