Strona główna / Artykuły / Wycofywanie referencji Latest-Value: Zdarzenia efektów z useEffectEvent

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.

917 słów

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.
  • Mniej bezsensownych ponownych wykonywań. Efekt zostaje uruchomiony tylko wtedy, gdy zmienia się wartość, z którą faktycznie synchronizuje się.
  • Jasniejszy model mentalny. Efekty oznaczają „utrzymaj ten system zsynchronizowanym z tym stanem”; wydarzenia efektów oznaczają „wykonaj to, gdy to się stanie”.
  • 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.
  • API jest niewielkie, ale opisuje problem, z którym boryka się niemal każdy deweloper React, nie wiedząc, jak go nazwać.
  • Literatura pokrewna