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.
- •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:
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:
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
- 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í.
- 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.