Lekce 4 · Hooky do hloubky
Refy (useRef)
Přístup k DOM, hodnoty bez překreslení, ref jako prop a useImperativeHandle.
Načítám lekci…
Lekce 4 · Hooky do hloubky
Přístup k DOM, hodnoty bez překreslení, ref jako prop a useImperativeHandle.
Načítám lekci…
V lekci o stavu jsme si řekli, že stav je sešit, který kuchaři drží React. Každý zápis do sešitu znamená nové vaření – nový render. To je přesně to, co chceme pro věci, které vidí hosté: počet v košíku, text v poli, otevřené menu.
Představ si to jako tabuli s denním menu na stěně restaurace. Když se něco změní, tabule se musí přepsat, aby ji hosté viděli aktuální. Přepisování ale stojí práci.
Kuchař si ale potřebuje pamatovat i věci, které hosté nikdy neuvidí: „minutka na troubě má číslo 3“, „poslední objednávka přišla v 18:42“. Kvůli tomu přece nebude přepisovat tabuli. Napíše si to na papírek a hodí do šuplíku pod linkou. Do šuplíku může sáhnout kdykoli, nikdo si toho nevšimne a nic se nepřepisuje.
Ten šuplík je v Reactu ref. Pamatuje si hodnotu mezi rendery (jako stav), ale jeho změna nespustí render.
Ref má ještě druhé použití. Normálně React stránku skládá za tebe a k jednotlivým HTML prvkům se nedostaneš – a většinou to ani nepotřebuješ. Jsou ale věci, které popisem („jak má stránka vypadat“) udělat nejdou: „dej kurzor do tohohle pole“, „odscrolluj sem“, „změř, jak je tenhle box velký“. Na to potřebuješ klíč od konkrétních dveří – odkaz na skutečný prvek stránky. I ten ti React předá v refu.
Typický případ pro šuplík: stopky s tlačítky Start a Stop. Interval spustíš v jednom handleru a zastavit ho musíš v jiném. K zastavení potřebuješ jeho ID, které vrátí setInterval. Kam ho uložit?
function Stopwatch() {const [seconds, setSeconds] = useState(0) // tabule – vidí ho uživatelconst intervalRef = useRef<number | null>(null) // šuplík – technický údajfunction start() {intervalRef.current = window.setInterval(() => {setSeconds((s) => s + 1)}, 1000)}function stop() {if (intervalRef.current !== null) {clearInterval(intervalRef.current)intervalRef.current = null}}return (<><p>{seconds} s</p><button onClick={start}>Start</button><button onClick={stop}>Stop</button></>)}
Co tu dělá ref:
useRef(null) vytvoří objekt { current: null }. React ti při každém renderu vrátí ten samý objekt – je to pořád ten samý šuplík.intervalRef.current můžeš kdykoli zapsat. React se o tom nedozví a nic nepřekreslí.Proč ne jinak?
| Kam uložit ID | Co se stane |
|---|---|
let id | Proměnná se při každém renderu vytvoří znovu (kuchař bez paměti). Po prvním tiknutí je ID ztracené a Stop nic nezastaví. |
useState | Funguje, ale uložení ID zbytečně spustí render – uživatel ID nikdy neuvidí. Tabuli přepisuješ kvůli papírku do šuplíku. |
useRef | Pamatuje si a nepřekresluje. Správně. |
| useState (tabule) | useRef (šuplík) | |
|---|---|---|
| Změna způsobí render | Ano | Ne |
| Hodnota v handleru | Snímek z renderu | Vždy aktuální (.current) |
| Jak se mění | Jen přes setter | Přímým zápisem do .current |
| Použij pro | Data zobrazená v UI | ID časovačů, prvky stránky, instance knihoven, předchozí hodnoty |
Co vidíš: Dvě počítadla. Horní je ve stavu (tabule), dolní v refu (šuplík). Text u refu ukazuje hodnotu, jakou měl ref při posledním renderu. Dole je počet renderů komponenty.
Vyzkoušej:
Co z toho plyne: Ref si hodnotu pamatuje stejně jako stav, ale jeho změna nic nepřekreslí. Na obrazovku se dostane jen náhodou, když render vyvolá něco jiného.
Načítám interaktivní část…
Druhé použití: klíč od konkrétních dveří. Typický příklad – vyhledávací pole s tlačítkem „Hledat“, které po kliknutí vrátí kurzor do pole, aby uživatel mohl hned psát dál.
function Search() {const inputRef = useRef<HTMLInputElement>(null)function handleSearch() {// …spusť hledání…inputRef.current?.focus() // vrať kurzor do pole}return (<><input ref={inputRef} /><button onClick={handleSearch}>Hledat</button></>)}
Kdy se do refu dostane prvek stránky:
Search(). V tu chvíli prvek <input> ještě neexistuje, inputRef.current je null.<input> a uvidí u něj ref={inputRef}. Vloží prvek do inputRef.current.focus() – přesně tu, kterou má každý <input> v prohlížeči.null.Proto má ref otazník: inputRef.current?.focus(). V handleru a v efektu je prvek k dispozici, během renderu ne.
Co vidíš: Tři typické úkoly, které popisem udělat nejdou: dát kurzor do pole, změřit velikost prvku a odscrollovat k položce seznamu. Každý používá ref na jiný prvek.
Vyzkoušej:
Co z toho plyne: Ref je klíč k prvku stránky. Používáš ho pro akce, které nejsou „jak má stránka vypadat“, ale „udělej teď tohle s tímhle prvkem“.
Načítám interaktivní část…
Když chceš dát kurzor do pole, které je schované uvnitř tvé vlastní komponenty (<FancyInput />), potřebuješ, aby ti ta komponenta ref „podala dál“. V Reactu 19 je to jednoduché: ref je obyčejný prop.
// React 19: ref přijde jako prop a pošleš ho dál na <input>function MyInput({ ref, ...props }: { ref?: Ref<HTMLInputElement> } & ComponentProps<'input'>) {return <input ref={ref} {...props} />}// Použití – jako by to byl obyčejný <input>const inputRef = useRef<HTMLInputElement>(null)<MyInput ref={inputRef} />// Starší zápis (React 18 a dřív) – v cizím kódu ho ještě potkášconst MyInput = forwardRef<HTMLInputElement, Props>((props, ref) => <input ref={ref} {...props} />)
Když rodiči podáš celý <input>, může s ním dělat cokoli: měnit styly, mazat obsah, přidávat posluchače. Je to jako dát někomu televizi se šroubovákem. Často je lepší dát mu jen dálkový ovladač s pár tlačítky. K tomu slouží useImperativeHandle:
interface FancyInputHandle {focus: () => voidshake: () => void}function FancyInput({ ref }: { ref?: Ref<FancyInputHandle> }) {const inputRef = useRef<HTMLInputElement>(null) // vlastní, soukromý refuseImperativeHandle(ref, () => ({focus: () => inputRef.current?.focus(),shake: () => { /* krátká animace zatřesení */ },}))return <input ref={inputRef} />}// Rodič vidí jen dvě tlačítka ovladače:const emailRef = useRef<FancyInputHandle>(null)emailRef.current?.shake()
<input> a nikomu ho nedá.useImperativeHandle řekne: „do refu, který mi poslal rodič, nedávej prvek stránky, ale tenhle objekt“.focus() a shake(). Vnitřek komponenty můžeš kdykoli předělat a rodič si ničeho nevšimne.Co vidíš: Komponenta FancyInput z příkladu výše. Rodič má dvě tlačítka, která volají metody z jejího „ovladače“.
Vyzkoušej:
useImperativeHandle. Všimni si, že rodič nikdy nedostane samotný <input>.Co z toho plyne: Komponenta rozhoduje, co dovolí rodiči ovládat. Malé, jasné API je bezpečnější než podat celý prvek.
Načítám interaktivní část…
Spoj ref s inputem a po kliknutí na tlačítko do něj přesuň kurzor.
VÝZVA: Zaměř pole
Ref je vytvořený, ale zatím s ničím nespojený.
1. Připoj ref k inputu: <input ref={inputRef} … />
2. Tlačítko ať při kliknutí zavolá inputRef.current?.focus()row, stack, card, list, btn, input nebo muted jsou hotové styly kurzu v src/styles.css (row = prvky vedle sebe, stack = pod sebou, card = rámeček, muted = šedý text).Načítám interaktivní část…
Chat procvičí kapitoly 1 a 2: ref na prvek stránky (scroll, focus) a ref jako paměť bez renderu. Odpočet kapitolu 3 – navrhneš vlastní dálkový ovladač.
Kombinace refu na prvek stránky, refu jako paměti bez renderu a efektu, který synchronizuje scroll.
Střední a těžké výzvy jsou bonus pro přihlášené. Registrace je zdarma – stačí e-mail, žádné heslo.
Navrhni komponentu s ovladačem o třech tlačítkách (start, pauza, reset) – přesně tak fungují přehrávače videa v UI knihovnách.
Střední a těžké výzvy jsou bonus pro přihlášené. Registrace je zdarma – stačí e-mail, žádné heslo.
forwardRef už nepotřebuješ.