React Hooks (háčky) — jsou funkce, které umožňují používat stav, efekty, kontext a další možnosti Reactu ve funkčních komponentách, bez psaní tříd. Představené v React 16.8, háčky radikálně změnily přístup k vývoji a nahradily složité vzory jako HOC a render props. Podle React, 2024 existuje 15 vestavěných háčků pokrývajících všechny scénáře správy komponent.
Hlavní
React Hooks (háčky) — sada vestavěných funkcí v Reactu, které umožňují ‘připojení’ k vnitřním mechanismům frameworku bez vytváření tříd. Před React 16.8 mohly funkční komponenty pouze přijímat props a vracet JSX — jakákoli složitá logika vyžadovala třídní komponentu.
Problém třídních komponent — oddělení související logiky. Kód týkající se jedné entity (například odběr WebSocket) byl nuceně rozdělen mezi componentDidMount, componentDidUpdate a componentWillUnmount. Háčky to řeší: veškerá logika se seskupí do jednoho useEffect.
Druhý problém — znovupoužití logiky. Pro znovupoužití stavové logiky mezi třídními komponentami bylo nutné použít HOC (komponenty vyššího řádu) nebo render props — oba vzory vytvářely další vnoření ve stromu komponent. Vlastní háčky řeší tento problém bez vnoření.
useState — nejzákladnější háček. Přijímá počáteční hodnotu a vrací tuple: aktuální hodnotu a funkci pro její aktualizaci. Na rozdíl od this.setState ve třídách, useState neslučuje objekty — nový stav zcela nahrazuje předchozí pro konkrétní volání useState.
import React, { useState } from 'react';
function Form() {
const [text, setText] = useState('');
const [submitting, setSubmitting] = useState(false);
async function handleSubmit() {
setSubmitting(true);
await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify({ text })
});
setSubmitting(false);
}
return (
<div>
<input
value={text}
onChange={e => setText(e.target.value)}
/>
<button onClick={handleSubmit}
disabled={submitting}>
{submitting ? 'Saving...' : 'Submit'}
</button>
</div>
);
}
Setter useState může přijmout funkci, která obdrží předchozí hodnotu a vrátí novou. Funkční forma je povinná, když se nový stav počítá z předchozího a jsou možné dávková volání setState — každé volání obdrží aktuální předchozí hodnotu.
useEffect — háček pro provádění vedlejších efektů ve funkčních komponentách. Přijímá dva argumenty: funkci-efekt a pole závislostí. React spouští efekt po každém renderu, ale při zadání závislostí — pouze když se některá z nich změní.
Prázdné pole závislostí [] znamená, že se efekt spustí jednou po prvním renderu (analogie componentDidMount). Návrat funkce z efektu je cleanup, který se spustí při odpojení (analogie componentWillUnmount) a před opětovným spuštěním efektu.
import React, { useState, useEffect } from 'react';
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const socket = new WebSocket(`wss://chat/${roomId}`);
socket.onmessage = (event) => {
setMessages(prev => [...prev, JSON.parse(event.data)]);
};
// Cleanup - zavřít socket
return () => socket.close();
}, [roomId]);
return <ul>
{messages.map((msg, i) =>
<li key={i}>{msg.text}</li>
)}
</ul>;
}
Závislosti useEffect — jsou jediným spojením mezi efektem a vnějším světem. Pokud je v efektu použita proměnná z props nebo stavu, musí být v poli závislostí. Ignorování tohoto pravidla je zdrojem chyb se zastaralými daty (stale closure).
useContext — háček pro čtení hodnoty kontextu. Přijímá objekt kontextu vytvořený pomocí React.createContext a vrací aktuální hodnotu. Pokud se kontext změní, komponenta se automaticky přerenderuje s novou hodnotou.
Před příchodem useContext vyžadoval přístup ke kontextu ve funkčních komponentách obalení v Context.Consumer, což vytvářelo vnoření. useContext zcela eliminuje Consumer — stačí zavolat háček na libovolném místě funkční komponenty.
import React, { createContext, useContext } from 'react';
const ThemeContext = createContext('světlý');
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button
className={`btn btn-${theme}`}
>
{theme === 'dark' ? 'moon' : '☀️'}
</button>;
}
useContext není náhradou za Redux nebo globální stav. React doporučuje používat kontext pro zřídka měnící se hodnoty — téma, jazyk, autorizace. Pro často aktualizovaná data každý re-render přepočítá všechny spotřebitele kontextu, což může snížit výkon.
useReducer — alternativa k useState pro stavy se složitou logikou aktualizace. Přijímá funkci reducer a počáteční stav, vrací aktuální stav a funkci dispatch. Reducer obdrží aktuální stav a akci, vrátí nový stav — čistá funkce bez vedlejších efektů.
useReducer je užitečný zejména když několik akcí (set, reset, increment, addItem) spravuje jeden stav. Místo několika useState s různými settery — jeden reducer s jasnými typy akcí. To činí logiku předvídatelnou a snadno testovatelnou.
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'setStep':
return { ...state, step: action.step };
case 'reset':
return { count: 0, step: 1 };
default:
throw new Error('Unknown action: ' + action.type);
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, {
count: 0,
step: 1
});
return (
<>
<p>Count: {state.count}</p>
<button
onClick={() => dispatch({ type: 'increment' })}>+</button>
<button
onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</>
);
}
useRef vytváří měnitelný objekt, který přetrvává mezi rendery. Změna current nezpůsobuje re-render. Hlavní použití — přístup k DOM prvkům a ukládání mutovatelných hodnot (časovače, předchozí hodnoty, instance knihoven).
useMemo memoizuje výsledek funkce. Přepočítává hodnotu pouze když se změní jedna ze závislostí. Používejte pro nákladné výpočty — filtrování velkých polí, řazení, formátování dat. useCallback — speciální případ useMemo pro memoizaci funkcí.
import React, { useRef, useMemo, useCallback } from 'react';
function SearchPage({ items, query, onItemSelect }) {
const inputRef = useRef(null);
// Autofocus při připojení
useEffect(() => {
inputRef.current?.focus();
}, []);
// Memoizovat filtrovaný výsledek
const filtered = useMemo(() => {
return items.filter(item =>
item.name.toLowerCase()
.includes(query.toLowerCase())
);
}, [items, query]);
// Memoizovat callback
const handleSelect = useCallback((id) => {
onItemSelect(id);
}, [onItemSelect]);
return (
<div>
<input ref={inputRef}
value={query}
onChange={e => setQuery(e.target.value)}
/>
<ul>
{filtered.map(item =>
<li key={item.id}>
<button
onClick={() => handleSelect(item.id)}>
{item.name}
</button>
</li>
)}
</ul>
</div>
);
}
Důležité pravidlo: useRef, useMemo a useCallback — nástroje optimalizace, nikoli povinné konstrukce. Neobalujte vše do useMemo — nejprve změřte výkon. Předčasná optimalizace komplikuje kód. React renderuje komponenty rychle; useMemo je potřeba pouze když jsou výpočty skutečně nákladné (O(n²) a výše).
React zavedl dvě přísná pravidla pro háčky. První: volejte háčky pouze na nejvyšší úrovni — ne uvnitř podmínek, cyklů nebo vnořených funkcí. Druhé: volejte háčky pouze z React funkčních komponent nebo vlastních háčků. ESLint plugin eslint-plugin-react-hooks automaticky kontroluje obě pravidla.
Nejčastější chyba — porušení pořadí volání háčků. React spoléhá na pořadí volání háčků mezi rendery. Pokud je háček uvnitř podmínky a nebyl zavolán v jednom z renderů, všechny následující háčky se posunou a způsobí chyby.
| Chyba | Co se děje | Řešení |
|---|---|---|
| Háček v podmínce | Pořadí háčků se láme | Přesuňte logiku podmínky dovnitř háčku |
| Stale closure | Efekt používá starou hodnotu | Přidejte všechny závislosti do pole |
| Nekonečná smyčka | useEffect bez závislostí mění stav | Určete závislosti nebo použijte useReducer |
| Změna ref v renderu | Vedlejší efekt v těle funkce | Přesuňte mutace do useEffect |
Druhá častá chyba — stale closure v useEffect. Pokud je v efektu použita proměnná neuvedená v závislostech, efekt ‘zachytí’ její hodnotu v okamžiku vytvoření uzávěru. Řešení — vždy zahrňte všechny použité proměnné do pole závislostí. Pokud je závislostí příliš mnoho, rozdělte efekt na několik částí.
Často kladené otázky
React spoléhá na pořadí volání háčků mezi rendery. Pokud je háček zavolán v podmínce a nefungoval v jednom z renderů, všechny následující háčky ‘kloužou’ — jejich stav se promíchá. Pravidlo ESLint hooks/exhaustive-deps na to upozorňuje.
useLayoutEffect je synchronní a provádí se před tím, než prohlížeč vykreslí změny. useEffect je asynchronní — po vykreslení. Používejte useLayoutEffect pouze když potřebujete změřit nebo změnit DOM dříve, než uživatel uvidí změny (pozice popover, obnovení posouvání).
Použijte AbortController uvnitř useEffect. Vytvořte controller, předejte signal do fetch a v cleanup funkci zavolejte controller.abort(). Pro staré prohlížeče postačí příznak cancelled: return () => { cancelled = true; } s kontrolou před setState.
Vlastní háček — JavaScriptová funkce s předponou use, která používá vestavěné háčky. Vytvořte ho, když se stejná logika s háčky opakuje ve dvou nebo více komponentách. Příklady: useDebounce, useLocalStorage, useMediaQuery. Vlastní háčky nahrazují HOC a render props.
V Strict Mode React záměrně připojí a odpojí komponentu dvakrát ve vývojovém režimu, aby našel chyby s nevyčištěnými efekty. Pokud je cleanup napsán správně, dvojité volání nezpůsobuje problémy. V produkčním režimu k tomu nedochází.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také