Lekce 1.8

Deep Dive: useEffect a useEffectEvent

useEffect je háček pro synchronizaci. Udržuje vaši komponentu v souladu s vnějším světem – serverem, DOMem, subscription. V praxi však rychle narazíte na dilema závislostí a „stale closures“.

Předpoklad: Tato funkce vyžaduje React 19.2 nebo novější. Příklady se soustředí na koncepty — nemusíte mít v této aplikaci spuštěný React 19, abyste myšlenky pochopili.

Co se naučíte
  • Proč je useEffect o synchronizaci, ne o libovolné logice
  • Jak vzniká dilema závislostí u useEffect
  • Proč změna hodnoty ne vždy znamená, že se má synchronizace restartovat
  • Jak useEffectEvent odděluje synchronizaci od reaktivity
  • Zlatá pravidla pro bezpečné používání useEffectEvent

useEffect: Synchronizace, ne libovolná logika

V jádru je useEffect synchronizační primitivum. Jeho úkolem je udržovat vaši komponentu v souladu s vnějším systémem — serverovým připojením, subscription, DOMem, časovačem atd.

Kontrakt Reactu je jednoduchý: efekt se spustí po prvním renderu a poté se znovu spouští vždy, když se změní závislosti, které tuto synchronizaci řídí.

Problém: moderní aplikace často potřebují číst čerstvá, reaktivní data uvnitř efektu, aniž by tyto hodnoty fungovaly jako spouštěče synchronizace.

Dilema závislostí: Chatovací místnost

Abychom viděli, proč na tom záleží, opusťme počítadla a podívejme se na realističtější příklad: chatovací místnost.

Cíl:

  • Připojit se k chat serveru podle roomId
  • Když se připojení povede, zalogovat zprávu včetně aktuálního tématu ("dark" / "light") pro analytiku

Přirozený první pokus může vypadat takto:

jsx
import { useEffect } from "react"

function ChatRoom({ roomId, theme }) {
  useEffect(() => {
    const connection = createConnection(roomId)
    connection.connect()

    connection.on("connected", () => {
      // ⚠️ We want to log the theme when we connect
      logConnection(roomId, theme)
    })

    return () => connection.disconnect()
  }, [roomId, theme]) // 👈 The problem is here

  return <h1>Welcome to {roomId}</h1>
}

Podle pravidel Reactu to vypadá správně: protože theme používáme uvnitř efektu, musí být v poli závislostí.

Z pohledu uživatele je to ale bug.

  • Požadované chování: přepnutí mezi světlým a tmavým režimem má jen přemalovat UI
  • Skutečné chování: protože theme je závislost, její změna zruší a znovu vytvoří spojení

Efekt je napsán správně, ale modeluje špatnou synchronizaci. Připojení závisí na roomId, ne na theme.

Vstupuje useEffectEvent: Oddělení synchronizace od reaktivity

Potřebujeme způsob, jak říct: „Chci číst tuto reaktivní hodnotu (theme), ale její změna nesmí znovu spouštět synchronizaci (připojení).“

useEffectEvent vám umožní vytáhnout reaktivní, ale nesynchronizační logiku do stabilního event handleru.

Zde je refaktorovaný ChatRoom s API Reactu 19.2:

jsx
import { useEffect, useEffectEvent } from "react"

function ChatRoom({ roomId, theme }) {
  // 1. Abstract the non-synchronizing logic
  // This function gets a stable identity
  const onConnected = useEffectEvent(() => {
    logConnection(roomId, theme)
  })

  useEffect(() => {
    const connection = createConnection(roomId)
    connection.connect()

    connection.on("connected", () => {
      // 2. Call the stable event handler inside the effect
      onConnected()
    })

    return () => connection.disconnect()
    // 3. 'theme' is no longer a dependency!
    //    'onConnected' is stable, so it doesn't need to be here either.
  }, [roomId])

  return <h1>Welcome to {roomId}</h1>
}

Naši logiku jsme rozdělili do dvou „košů“:

  • Závislosti (roomId): hodnoty, jejichž změna vyžaduje resynchronizaci (disconnect / reconnect)
  • Effect Events (theme): hodnoty, které chceme číst, když nastane událost, aniž bychom efekt restartovali

Jak useEffectEvent funguje

Za useEffectEvent stojí dvě klíčové myšlenky.

1. Stabilní identita

Funkce vrácená z useEffectEvent (onConnected v našem příkladu) je referenčně stabilní. Z pohledu Reactu nikdy mezi rendery „nevypadá jinak“.

Protože je její identita stabilní, nedáváte ji do dependency array — a nezpůsobí znovuspuštění efektu, když se props nebo state použité uvnitř změní.

2. Čerstvé hodnoty

I když je vrácená funkce stabilní, při zavolání uvnitř efektu se „teleportuje“ do nejnovějšího renderu.

To znamená, že vždy vidí nejčerstvější hodnoty props a state (např. aktuální theme), aniž by musel efekt, který ji volá, reagovat na změny těchto hodnot.

Zlatá pravidla useEffectEvent

  1. Funkce z useEffectEvent volejte pouze uvnitř efektů.

    Jsou navrženy tak, aby se používaly z useEffect (a podobných efektových hooků), ne během renderování.

  2. Nepředávejte funkce z useEffectEvent jako props.

    Jsou optimalizované pro použití uvnitř komponenty, která je definuje. Pro event handlery předávané do dětí nadále používejte běžné funkce nebo useCallback.

Myslete na useEffectEvent jako na chybějící dílek, který dovolí udržet efekty čistě synchronizační, a přitom číst nejnovější reaktivní data, když vnější systémy vyvolají události.

Shrnutí

useEffect je o synchronizaci: připojení, subscription, naslouchání a úklidu, když se změní podmínky synchronizace.

Dilema závislostí vzniká, když potřebujete číst měnící se hodnoty uvnitř efektu, ale nechcete, aby právě tyto hodnoty řídily restart efektu.

useEffectEvent čistě odděluje synchronizaci (závislosti efektu) od reaktivity (hodnoty, které čtete při událostech) a poskytuje čerstvé hodnoty bez zbytečných teardownů.

Jakmile toto rozdělení vstřebáte, budete psát efekty, které jsou jednodušší, předvídatelnější a výrazně méně náchylné k chybám ve složitých aplikacích.