Wycofywanie referencji Latest-Value: Zdarzenia efektów z useEffectEvent
Dowiedz się, jak useEffectEvent w React 19.2 oddziela logikę nierеакtywną od efektu, eliminuje triki z użyciem ref latest-value oraz wskazuje, kiedy ten hook nie jest odpowiednim narzędziem.
useRef, której jedynym zadaniem jest przechowywanie „najnowszej wartości”, aby efekt nie musiał od niej zależeć. To rozwiązanie działa, ale ukrywa intencję twórcy kodu. React 19.2 uczynił funkcję useEffectEvent stabilną, dając temu wzorcowi hook pierwszej klasy. Pod koniec kursu będziesz mógł rozdzielić logikę reaktywną i nierreaktywną w obrębie efektu oraz wiedzieć, kiedy dany hook nie ma zastosowania.
Miara zależności, która nie może zwyciężyć
Konflikt pojawia się przy długotrwałych połączeniach: salach czatowych, strumieniach WebSocket, sesjach analitycznych, subskrypcjach. Komponent ChatRoom otrzymuje parametry roomId i theme. Efekt musi nawiązać ponowne połączenie, gdy zmieni się roomId, ale odczytuje wartość theme jedynie w celu ustawienia stylu komunikatu po nawiązaniu połączenia.
Dodanie theme do tablicy zależności powoduje, że przełączenie na tryb ciemny niespodziewanie zamyka połączenie. Jeśli go pominąć, reguła lintingu dotycząca hooków wydaje komunikat; jeśli ją uciszyć, funkcja zwrotna zachowuje wartość theme z poprzedniej renderizacji, co stanowi klasyczny przypadek zastarzałego zamknięcia.
function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(roomId);
connection.on('connected', () => {
showNotification('Connected!', theme); // stale after theme changes
});
connection.connect();
return () => connection.disconnect();
}, [roomId, theme]); // theme shouldn't trigger a reconnect
}
Rozwiązanie zastosowane przez większość zespołów
Zwykłe rozwiązanie polegało na kopiowaniu wartości theme do ref-u przy każdej renderizacji oraz odczytywaniu ref.current w funkcji zwrotnej. Działa to, ale krok synchronizacji łatwo zostać zapomnianym, a w kodzie nie ma żadnej wskazówki, że ta logika jest celowo nierеакtywna.
Rozdzielenie logiki zdarzeń za pomocą useEffectEvent
useEffectEvent wyodrębnia nierеакtywną część efektu do osobnej funkcji. Funkcja ta zawsze ma dostęp do najnowszych wartości props i stanu, jej wywołanie nigdy nie powoduje ponownego uruchomienia efektu, a sama funkcja nie trafia do tablicy zależności.
Poniżej powiadomienie przechodzi do funkcji onConnected. Ta funkcja jedynie nawiązuje połączenie, rejestruje obsługę zdarzeń, łączy się i rozłącza podczas czyszczenia stanu.
import { useEffect, useEffectEvent } from 'react';
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme); // always fresh, never a dependency
});
useEffect(() => {
const connection = createConnection(roomId);
connection.on('connected', () => onConnected());
connection.connect();
return () => connection.disconnect();
}, [roomId]); // only roomId matters now
}
Jako zależność pozostaje już tylko roomId. theme nie został ukryty przed narzędziem lintera – został jedynie przeklasyfikowany. Wyświetlanie komunikatu po nawiązaniu połączenia jest reakcją na zdarzenie, więc stanowi logikę związaną z zdarzeniami, a nie synchronizację. Zmiana tematu już nie powoduje ponownego nawiązania połączenia, a kolejny komunikat nadal wykorzystuje obecny temat.
Koncepcyjnie to właśnie robił ręcznie „hack” z ref: React zapewnia, że funkcja wywoływana w ramach efektu odnosi się do najnowszej wartości z renderowania, dzięki czemu intencja jest teraz widoczna.
Kozyści
- Brak już przestarzałych zamykających wartości. Funkcja Effect Event odczytuje najnowsze wartości bez konieczności ręcznych aktualizacji ref.
Dokumentacja React od czasów wersji 18 zalecała programistom oddzielenie wydarzeń od efektów; wersja 19.2 w końcu popiera tę zalecenie dzięki stabilnemu hookowi. Więcej informacji na temat pozostałych zmian znajdziesz w naszym przeglądzie Activity, useEffectEvent i statycznego renderowania w React 19.2.
Zanim go użyjesz
useEffectEvent nie zastępuje useEffect, ani nie jest ogólnym sposobem na zmniejszenie tablic zależności.
- Używaj go tylko wtedy, gdy zmiana wartości nie powinna restartować efektu. Jeśli tak ma być, wartość ta powinna znajdować się w tablicy.
- Nigdy nie wywołuj Effect Event podczas renderowania; należy to robić z wnętrza efektów, takich jak funkcje zwrotne subskrypcji lub timerzy.
- Zachowaj go lokalnie w komponencie, zamiast przekazywać go potomkom jako prop.
- Jeśli większa część efektu znajduje się wewnątrz Effect Event, logika ta prawdopodobnie powinna być umieszczona w zwykłym obsługowniku zdarzeń.
Przed wersją 19.2 hook istniał tylko w wersjach eksperymentalnych, więc projekty na wersjach 19.0 lub 19.1 muszą zostać zaktualizowane, aby korzystać ze stabilnej API.
Główne wnioski
- Każde rozwiązanie typu „przechowywanie najnowszej wartości w ref” jest kandydatem na użycie
useEffectEvent. - Dla każdej zależności sprawdź, czy zmiana powinna restartować efekt; jeśli nie, kod, który ją odczytuje, stanowi logikę zdarzenia.
Literatura pokrewna
- Wybór odpowiedniego narzędzia dla React: Derive, Handle, Fetch, Defer czy Effect — Przewodnik decyzyjny dotyczący zastępowania refleksywnych wywołań useEffect wartościami pochodnymi, obsługiwanymi zdarzeniami, warstwą danych, useTransition, measured useMemo oraz API React 19.
- Przestań synchronizować stan z useEffect: Bezpieczniejszy wzorzec w React — Dowiedz się, dlaczego używanie useEffect do synchronizacji stanu pochodnego powoduje sytuacje konkurencyjne i dodatkowe renderowania, oraz jak zastąpić to pochodzeniem wartości w czasie renderowania i użyciem atrybutu key.
- Stwórz toy useState, aby zrozumieć snapshots, queues i effects — Dowiedz się, jak zachowują się hooki React, budując mały useState, a następnie wykorzystaj ten model do wyjaśnienia kolejek aktualizacji, przestarzałych snapshotów, zamknięć funkcji, czyszczenia efektów, refów oraz grupowania zadań.
- Async Forms w React 19 z use, useActionState i useOptimistic — Jak use(), useActionState, useFormStatus i useOptimistic zastępują ręczne mechanizmy ładowania i flagi błędów w React 19, wraz z pułapkami, które każdy z tych hooków potajemnie ukrywa.
- Wycofywanie legacy zależności JavaScript: CRA, Moment, jQuery i inne — Które stare biblioteki i narzędzia JavaScript należy stopniowo wycofywać, dlaczego każde z nich stanowi obciążenie, co je zastępuje oraz jak zapobiec ponownemu pogorszeniu się listy zależności.