useEffectEvent w React

Podstawowy przewodnik - co robić i czego unikać

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ą.

Kluczowa koncepcja: useEffectEvent tworzy stabilną referencję funkcji, która zawsze odczytuje najnowsze wartości, ale nie wyzwala ponownego wykonania 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.

❌ Tradycyjny problem:
useEffect(() => {
  logVisit(roomId, theme); // Uruchamia się ponownie, gdy zmienia się motyw
}, [roomId, theme]); // Musieliśmy uwzględnić motyw!
✅ Z useEffectEvent:
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 useEffectEvent tylko 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.