Czym jest useEffectEvent?
useEffectEvent jest eksperymentalnym hookiem React, który pozwala wyodrębnić niereaktywną logikę z Efektów. Rozwiązuje częsty problem, w którym musisz odczytać najnowszą wartość propsów lub stanu wewnątrz Efektu bez powodowania ponownego uruchomienia tego Efektu, gdy te wartości się zmieniają.
Problem, który rozwiązuje
Rozważmy aplikację czatu, w której chcesz zarejestrować wiadomość, gdy pokój się zmienia, ale musisz uwzględnić bieżący motyw w swoim logu. Tradycyjnie musiałbyś dodać motyw do tablicy zależności, co powodowałoby ponowne uruchomienie Efektu za każdym razem, gdy motyw się zmienia, mimo że chcesz reagować tylko na zmiany pokoju.
useEffect(() => {
logVisit(roomId, theme); // Uruchamia się ponownie, gdy zmienia się motyw
}, [roomId, theme]); // Musieliśmy uwzględnić motyw!
const onVisit = useEffectEvent((roomId) => {
logVisit(roomId, theme); // Odczytuje najnowszy motyw
});
useEffect(() => {
onVisit(roomId); // Uruchamia się ponownie tylko gdy zmienia się roomId
}, [roomId]);
Co robić i czego unikać
✓ RÓB
- Używaj tego, aby odczytać najnowsze propsy/stan bez dodawania ich do zależności
- Wywołuj to bezpośrednio z wnętrza Efektów
- Używaj tego dla obsługi zdarzeń, które muszą mieć dostęp do kontekstu Efektu
- Używaj tego, aby oddzielić logikę reaktywną od niereaktywnej
- Wywołuj to synchronicznie w swoim Efekcie
✗ NIE RÓB
- Nie wywołuj tego ze zwykłych obsługi zdarzeń
- Nie wywołuj tego podczas renderowania
- Nie przekazuj tego jako prop do komponentów
- Nie wywołuj tego asynchronicznie lub z opóźnieniem
- Nie używaj tego jako zamiennika dla właściwej memoizacji
Szczegółowe przykłady
✅ RÓB: Wyodrębniaj niereaktywną logikę
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Połączono!', theme);
});
useEffect(() => {
const connection = createConnection(roomId);
connection.on('connected', onConnected);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // Tylko roomId jest reaktywne
}
Ten Efekt łączy się ponownie tylko gdy roomId się zmienia, ale onConnected zawsze używa najnowszego theme.
❌ NIE RÓB: Wywołuj z obsługi zdarzeń
function Component() {
const onClick = useEffectEvent(() => {
// ❌ Źle! Nie używaj w obsłudze zdarzeń
doSomething();
});
return <button onClick={onClick}>Kliknij</button>;
}
Zamiast tego użyj zwykłych funkcji lub useCallback dla obsługi zdarzeń.
✅ RÓB: Odczytywanie najnowszych propsów w Efektach
function Timer({ interval, onTick }) {
const onTickEvent = useEffectEvent(() => {
onTick(); // Zawsze wywołuje najnowszy onTick
});
useEffect(() => {
const id = setInterval(onTickEvent, interval);
return () => clearInterval(id);
}, [interval]); // Tylko interval jest reaktywny
}
❌ NIE RÓB: Wywołuj asynchronicznie
function Component() {
const onData = useEffectEvent((data) => {
processData(data);
});
useEffect(() => {
fetchData().then(data => {
onData(data); // ❌ Ryzykowne! Wywołane asynchronicznie
});
}, []);
}
Funkcja może być wywołana po odmontowaniu komponentu lub po zmianie wartości.
✅ RÓB: Łącz z logiką czyszczenia
function Analytics({ userId, page }) {
const logPageView = useEffectEvent(() => {
analytics.track('page_view', { userId, page });
});
useEffect(() => {
logPageView();
return () => {
// Czyszczenie może również używać Effect Events
const logPageExit = useEffectEvent(() => {
analytics.track('page_exit', { userId, page });
});
logPageExit();
};
}, []); // Puste zależności - uruchamia się raz na montowanie
}
❌ NIE RÓB: Przekazuj jako zależności do innych hooków
function Component() {
const onEvent = useEffectEvent(() => {
doSomething();
});
// ❌ Nie rób tego
const memoized = useMemo(() => {
return onEvent();
}, [onEvent]);
}
Typowe przypadki użycia
1. Logowanie i analityka
Gdy musisz rejestrować zdarzenia z najnowszymi preferencjami użytkownika lub ustawieniami bez ponownego subskrybowania.
const logEvent = useEffectEvent((eventName) => {
analytics.log(eventName, { theme, locale, userId });
});
useEffect(() => {
logEvent('page_visit');
}, [pathname]); // Reaguj tylko na zmiany pathname
2. Callbacki z najnowszym stanem
Podczas przekazywania callbacków do bibliotek zewnętrznych, które nie powinny powodować ponownych subskrypcji.
const onMessage = useEffectEvent((msg) => {
showToast(msg, { variant: userPreference });
});
useEffect(() => {
const unsubscribe = messageService.subscribe(onMessage);
return unsubscribe;
}, []); // Subskrybuj raz, callback używa najnowszego userPreference
3. Debouncing z najnowszymi wartościami
Podczas implementacji debouncingu, używając zawsze najnowszej logiki callbacku.
const onSearch = useEffectEvent(() => {
performSearch(query, filters, sortBy);
});
useEffect(() => {
const timeoutId = setTimeout(onSearch, 500);
return () => clearTimeout(timeoutId);
}, [query]); // Debounce query, ale używaj najnowszych filters/sortBy
Najlepsze praktyki
- Używaj
useEffectEventtylko wtedy, gdy zidentyfikowałeś rzeczywistą potrzebę odczytu wartości niereaktywnych - Zastanów się, czy Twoja logika naprawdę należy do Efektu, czy może powinna być w obsłudze zdarzeń
- Utrzymuj funkcję Event skoncentrowaną na jednym celu
- Dokumentuj, dlaczego jej używasz, aby pomóc przyszłym opiekunom kodu
- Poczekaj na stabilne wydanie przed użyciem w aplikacjach produkcyjnych
Ścieżka migracji
Jeśli obecnie używasz useCallback z ciągle zmieniającymi się zależnościami lub tłumisz linter komentarzami eslint-disable, useEffectEvent może być rozwiązaniem, którego potrzebujesz. Jednak najpierw rozważ, czy restrukturyzacja logiki komponentu nie byłaby bardziej odpowiednia.
Podsumowanie
useEffectEvent jest potężnym narzędziem do rozwiązania wyzwania odczytu najnowszych wartości w Efektach bez powodowania niepotrzebnych ponownych wykonań. Postępując zgodnie z tymi wskazówkami, będziesz w stanie używać go efektywnie, gdy stanie się stabilny. Pamiętaj, że jest przeznaczony do konkretnych scenariuszy, w których musisz oddzielić logikę reaktywną od niereaktywnej w Efektach.