React 09 · Výkon🔓 zdarma po přihlášení
Výkon a renderování
Kdy se komponenta překresluje, memo, useMemo, useCallback, useTransition, useDeferredValue.
Načítám lekci…
React 09 · Výkon🔓 zdarma po přihlášení
Kdy se komponenta překresluje, memo, useMemo, useCallback, useTransition, useDeferredValue.
Načítám lekci…
Vrátíme se do kuchyně. Šéfkuchař (rodičovská komponenta) má pod sebou pomocníky (dětské komponenty). Kdykoli šéf změní svůj recept – třeba přidá sůl –, všichni pomocníci znovu projdou svůj recept a zkontrolují, jestli jejich talíř pořád odpovídá. Většinou zjistí, že ano, a nic nemění. Takovému projití receptu se říká render.
Projít recept je rychlé. Drahé je teprve skutečně něco vyměnit na stole – a to React dělá jen tam, kde se výsledek opravdu liší. Proto je většina renderů levná a není třeba je řešit.
Problém nastane, když má některý pomocník hodně práce – třeba přepočítává tabulku s tisíci řádky – a ostatní na něj musí čekat. Pak je aplikace pomalá: píšeš do pole a písmena naskakují se zpožděním. Tahle lekce je o tom, jak takové čekání najít a odstranit.
memo); odložení méně důležité práce na později (useTransition).Komponenta se renderuje (React zavolá její funkci), když:
Bod 2 překvapí nejvíc. Příklad:
function Dashboard() {const [note, setNote] = useState('')return (<><input value={note} onChange={(e) => setNote(e.target.value)} /><ExpensiveChart year={2025} /></>)}
note v Dashboard.Dashboard(). Ta vrátí JSX s <ExpensiveChart year={2025} />.ExpensiveChart() – i když year je pořád 2025. React props sám od sebe neporovnává. Šéf změnil recept, pomocník musí projít ten svůj.Dvě techniky, které zrychlí aplikaci bez jediného memo. Obě vycházejí z toho, že se překresluje komponenta se změněným stavem a všechno pod ní – nic nad ní ani vedle ní.
V příkladu výše potřebuje note jen input. Graf o něm neví. Tak proč je stav v komponentě, která obsahuje i graf? Přesuneme ho do samostatné komponenty:
function Dashboard() {return (<><NoteInput /> {/* stav note je uvnitř */}<ExpensiveChart year={2025} /></>)}function NoteInput() {const [note, setNote] = useState('')return <input value={note} onChange={(e) => setNote(e.target.value)} />}
Teď změna note překreslí jen NoteInput. Dashboard ani graf se nedozví nic – sůl se změnila jen v jedné omáčce.
Co když stav musí být v obalující komponentě – třeba barva pozadí, kterou používá obal kolem grafu? Pak pomůže trik s children:
// ❌ ColorPicker vytváří graf sám → při změně barvy ho překreslífunction ColorPicker() {const [color, setColor] = useState('white')return (<div style={{ background: color }}><input type="color" onChange={(e) => setColor(e.target.value)} /><ExpensiveChart year={2025} /></div>)}// ✅ Graf vytvoří rodič a předá ho jako childrenfunction ColorPicker({ children }: { children: ReactNode }) {const [color, setColor] = useState('white')return (<div style={{ background: color }}><input type="color" onChange={(e) => setColor(e.target.value)} />{children}</div>)}<ColorPicker><ExpensiveChart year={2025} /></ColorPicker>
Proč to funguje? Prvek <ExpensiveChart /> teď vznikl v rodiči ColorPickeru. Když se změní barva, React překreslí ColorPicker, ale children je pořád ten samý prvek z minula – nikdo ho znovu nevytvořil. React pozná, že se nezměnil, a graf přeskočí. Hotové jídlo přinesené z jiné kuchyně se nepřevařuje.
Když uspořádání nestačí, přijde na řadu pomocník, který se před prací podívá na objednávku: „Je stejná jako minule? Pak nic nedělám a podám minulý talíř.“ To je memo.
const ExpensiveChart = memo(function ExpensiveChart({ year }: { year: number }) {// …drahý výpočet…})
Má to ale háček. Pomocník neporovnává objednávky po obsahu, ale podle toho, jestli je to tentýž lístek (Object.is pro každý prop zvlášť). U čísel a textů je to totéž. Ale objekty a funkce vznikají při každém renderu rodiče znovu – je to nový lístek, i když je na něm napsané totéž:
function Dashboard() {const [note, setNote] = useState('')// Při každém renderu NOVÝ objekt a NOVÁ funkceconst options = { year: 2025, color: 'blue' }const handleClick = (point: Point) => console.log(point)return <ExpensiveChart options={options} onPointClick={handleClick} />// memo porovná: options === minulé options? NE → překreslí se stejně}
Řešením je ty hodnoty zapamatovat, aby rodič při dalším renderu podal tentýž lístek:
const options = useMemo(() => ({ year: 2025, color: 'blue' }), []) // stejný objektconst handleClick = useCallback((point: Point) => console.log(point), []) // stejná funkce
| Nástroj | Co dělá | Kdy použít |
|---|---|---|
memo(Comp) | Přeskočí render, když jsou všechny props stejné (podle reference). | Drahá komponenta, která se často překresluje se stejnými props. |
useMemo(fn, deps) | Zapamatuje VÝSLEDEK výpočtu. | Drahý výpočet; nebo stabilní objekt/pole pro memo komponentu či závislost efektu. |
useCallback(fn, deps) | Zapamatuje FUNKCI (= useMemo(() => fn)). | Funkce předávaná memo komponentě nebo použitá v závislostech efektu. |
Co vidíš: Rodič s počítadlem a čtyři dětské karty. Karta žlutě blikne, když se renderuje. Karty se liší v tom, jestli jsou obalené v memo a jaké props dostávají.
Vyzkoušej:
memo. Dostává objekt style a funkci vytvořené přímo v renderu – pokaždé nový lístek.useMemo a useCallback. Nebliká.Co z toho plyne: memo pomůže jen tehdy, když jsou props opravdu stejné reference. Jediný objekt nebo funkce vytvořená v renderu ho vyřadí.
Načítám interaktivní část…
V restauraci má přednost host, který právě vešel – musíš ho pozdravit hned. Leštění příborů může počkat, a když při něm vejde další host, leštění přerušíš a vrátíš se k němu později.
React umí totéž. Některé aktualizace jsou urgentní – psaní do pole, kliknutí; uživatel čeká okamžitou odezvu. Jiné jsou neurgentní – překreslení velkého seznamu výsledků. React umí neurgentní render začít, a když přijde urgentní, přerušit ho a vrátit se k němu později. Musíš mu jen říct, co je co.
function Search() {const [query, setQuery] = useState('')const deferredQuery = useDeferredValue(query) // „opožděná kopie“return (<><input value={query} onChange={(e) => setQuery(e.target.value)} /><SlowList query={deferredQuery} /></>)}const SlowList = memo(function SlowList({ query }: { query: string }) { … })
Co se stane, když napíšeš písmeno „a“:
query je „a“, ale deferredQuery je pořád stará hodnota „“. Input ukáže „a“ hned. SlowList dostane stejný prop jako minule a díky memo se přeskočí.deferredQuery je „a“ – teď se SlowList počítá.Co vidíš: Pole a pod ním seznam 400 uměle zpomalených položek (každý render seznamu trvá asi 120 ms). Zaškrtávátko zapíná useDeferredValue. Když seznam ukazuje starou hodnotu, zprůhlední.
Vyzkoušej:
Co z toho plyne: Pomalý seznam pořád trvá stejně dlouho. Ale už neblokuje psaní – React ho počítá „mezi písmeny“ a přerušuje ho.
Načítám interaktivní část…
Když máš přístup k setteru, můžeš neurgentní změnu označit přímo. Typicky přepínání záložek: kliknutí má odezvu hned, ale vykreslení obsahu pomalé záložky může počkat – a když mezitím klikneš na jinou záložku, React rozpracovanou práci zahodí.
const [tab, setTab] = useState('about')const [isPending, startTransition] = useTransition()function selectTab(next: Tab) {startTransition(() => setTab(next)) // tahle změna je neurgentní}{isPending && <Spinner />} // zatímco se nová záložka počítá
Co vidíš: Tři záložky. „Příspěvky“ jsou uměle pomalé (render trvá asi 300 ms). Zaškrtávátko zapíná useTransition.
Vyzkoušej:
Co z toho plyne: Transition dělá z pomalého přepnutí přerušitelnou práci na pozadí. Uživatel může klikat dál a vidí, že se něco děje.
Načítám interaktivní část…
| useTransition | useDeferredValue | |
|---|---|---|
| Co obalíš | Aktualizaci stavu (setX) | Hodnotu |
| Kdy | Máš přístup k setteru (přepínání záložek, navigace) | Hodnota přichází zvenku (props), nebo chceš mít pole okamžitě a seznam později |
| Jak poznáš čekání | isPending | value !== deferredValue |
lazy(() => import('./Page')) + Suspense. Prohlížeč stáhne kód stránky, až když ho potřebuje. Tenhle kurz načítá každou lekci zvlášť.// Líné načtení těžké komponenty (graf, editor, mapa)const Chart = lazy(() => import('./Chart'))<Suspense fallback={<Spinner />}>{showChart && <Chart data={data} />}</Suspense>
Zkus psát do poznámky a sleduj, jak cena bliká. Jedním obalením to zastavíš.
Zadání najdeš v komentářích v kódu níže – na začátku souboru a u míst označených TODO.
Načítám interaktivní část…
První výzva je přesně příklad z kapitol 1–3: oprav ji nejdřív uspořádáním kuchyně, pak memoizací, a porovnej obě řešení. Druhá procvičí kapitolu 4 – pole musí reagovat okamžitě, i když se seznam počítá déle.
Najdi, proč je psaní pomalé, a oprav to – nejdřív strukturou, pak memoizací. Co se má dít, je v komentářích v kódu.
Střední a těžké výzvy se odemknou, jakmile se přihlásíš. Stejně jako celý kurz jsou zdarma – stačí e-mail, žádné heslo ani platba.
Host u dveří (psaní) má přednost před leštěním příborů (seznam výsledků). Co se má dít, je v komentářích v kódu.
Střední a těžké výzvy se odemknou, jakmile se přihlásíš. Stejně jako celý kurz jsou zdarma – stačí e-mail, žádné heslo ani platba.
children.useMemo/useCallback.useRenderFlash)useRenderFlash – ze souboru src/course/useRenderFlash.ts. Pomocný hook jen pro výuku: element blikne při každém renderu, ať je vidět, co se překresluje. Ve vlastním kódu ho nepotřebuješ./*** A teaching helper hook: the element briefly "flashes" after every render of the component.* An effect without a dependency array runs after every commit – exactly what we want to visualize.*/export function useRenderFlash<T extends HTMLElement>() {const ref = useRef<T>(null)useEffect(() => {const el = ref.currentif (!el) returnel.classList.remove('flash')void el.offsetWidth // restart the CSS animationel.classList.add('flash')})return ref}
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).