React 01 · Základy
Rychlé opakování základů
JSX, komponenty, props, children, podmínky, seznamy a proč na key záleží.
Načítám lekci…
React 01 · Základy
JSX, komponenty, props, children, podmínky, seznamy a proč na key záleží.
Načítám lekci…
Tahle lekce je rychlé opakování základů. I když je trochu znáš, projdi si ji – zavádí představy, na kterých staví celý kurz. A ukázka s key na konci chytí i zkušené vývojáře.
Představ si restauraci. Host si neobjednává postupem („rozpal pánev, dej na ni máslo, rozklepni dvě vejce…“). Řekne co chce: „omeletu se šunkou“. Jak se to udělá, je starost kuchyně.
Přesně tak funguje React. Ty mu neříkáš, jak má měnit stránku („najdi tenhle odstavec, přepiš mu text, přidej třídu“). Popíšeš, jak má stránka vypadat, a React zařídí, aby tak vypadala. Tomuhle přístupu se říká deklarativní: popisuješ výsledek, ne postup.
Recept na omeletu je jeden, ale omelet se uvaří stovky – pokaždé trochu jiná: se sýrem, bez cibule, velká porce. Recept tedy počítá s přáním hosta.
Ze stejné objednávky má vždy vzniknout stejné jídlo. A kuchař objednávku nepřepisuje – nemůže hostovi změnit, co si objednal. Tohle jsou dvě nejdůležitější pravidla komponent a za chvíli je uvidíš v kódu.
Velké menu se skládá z menších receptů: „polední menu“ = polévka + hlavní jídlo + dezert. Stejně se aplikace skládá z komponent: stránka obsahuje hlavičku, seznam a patičku, seznam obsahuje položky, položka obsahuje tlačítko. Každá komponenta řeší jen svůj kousek a skládají se do sebe jako kostky.
Přirovnání je hezké, ale jak to vypadá v kódu? Projdeme to po malých krocích – od obyčejného HTML až k první komponentě. Žádný krok nepřeskočíme.
Takhle vypadá pozdrav na stránce v čistém HTML:
<section class="welcome"><h1>Ahoj, Jana!</h1><p>Dobré ráno</p></section>
Je to hotové jídlo napsané natvrdo. Pořád stejné – pro Janu, ráno. Kdybychom chtěli pozdravit Petra odpoledne, musíme napsat celé HTML znovu.
Bez Reactu bys stránku měnil JavaScriptem krok po kroku – přesně jako host, který kuchaři diktuje postup:
const section = document.createElement('section') // vezmi pánevconst h1 = document.createElement('h1') // rozklepni vejceh1.textContent = 'Ahoj, Jana!'section.append(h1)document.body.append(section)// …a když se změní jméno, musíš si sám pamatovat, kde je h1, a přepsat ho:h1.textContent = 'Ahoj, Petře!'
U jednoho nadpisu to jde. U celé aplikace se v tom rychle ztratíš: na co všechno se musí sáhnout, když se něco změní?
React to otočí. Místo postupu napíšeš funkci, která vrátí, jak má kus stránky vypadat. HTML se přesune dovnitř JavaScriptu – tomuhle zápisu se říká JSX:
function Welcome() {return (<section className="welcome"><h1>Ahoj, Jana!</h1><p>Dobré ráno</p></section>)}
function Welcome() je recept – obyčejná funkce. Jméno začíná velkým písmenem.return ( … ) vrací popis jídla: jak má výsledek vypadat. Nic se tu ještě nevykresluje.className místo class (slovo class má JavaScript zabrané).Zatím umí recept jen Janu. Přidáme mu objednávku – parametr se jménem. Typ za dvojtečkou ({ name: string }) znáš z lekce TypeScript 01: objednávka musí obsahovat jméno jako text. Ve složených závorkách { } pak můžeš do JSX vložit hodnotu z JavaScriptu:
function Welcome({ name }: { name: string }) {return (<section className="welcome"><h1>Ahoj, {name}!</h1></section>)}
Recept je hotový. Objednávka se píše jinam – do JSX jiné komponenty, která pozdrav chce ukázat. Tady je to hlavní komponenta App, která recept použije dvakrát:
function App() {return (<div>{/* Jeden recept, dvě objednávky → dvě jídla */}<Welcome name="Jana" /><Welcome name="Petr" /></div>)}
<Welcome name="Jana" /> vypadá jako HTML tag a tak se komponenta i používá. Atribut name se jí předá jako objednávka – v Reactu se jí říká props (podrobně v kapitole 2).
Recept sám nic neuvaří. Někde na začátku aplikace (v souboru main.tsx) je jediný řádek, který Reactu řekne: „tady je hlavní recept, uvař ho do tohohle místa na stránce“:
createRoot(document.getElementById('root')).render(<App />)
Od té chvíle vaří React. Zavolá App, ta obsahuje další komponenty (třeba Welcome), React zavolá i ty – a z jejich popisů postaví stránku. Když se později něco změní, zavolá recepty znovu a na stránce upraví jen to, co se opravdu liší. Postup z kroku 2 za tebe napíše on.
| V kuchyni | V Reactu | V kódu |
|---|---|---|
| recept | komponenta | function Welcome() { … } |
| objednávka | props | <Welcome name="Jana" /> |
| hotové jídlo | JSX – popis výsledku | return <h1>Ahoj, {name}!</h1> |
| kuchař | React | render(<App />) |
| menu složené z jídel | strom komponent | <App> → <Welcome> → … |
Co vidíš: Dvě karty vykreslené stejnou komponentou Welcome. Levá dostává objednávku z políček, pravá má objednávku napevno (Petr, odpoledne).
Vyzkoušej:
Welcome je obyčejná funkce, která vrací JSX.Co z toho plyne: Komponenta je recept: popisuje, jak má výsledek vypadat pro danou objednávku. Vaření – i opakované vaření po každé změně – obstará React.
Načítám interaktivní část…
Teď už víš, co komponenta je a k čemu slouží. V kapitole 1 se podíváme, co JSX vlastně je a jaká má pravidla.
JSX z kroku 3 vypadá jako HTML zapsané uprostřed JavaScriptu. Ve skutečnosti je to jen pohodlnější zápis volání funkce. Před spuštěním se přeloží na obyčejný JavaScript, který vytvoří objekt s popisem prvku:
// Tohle napíšeš:<Button color="blue">Uložit</Button>// A tohle z toho vznikne:jsx(Button, { color: 'blue', children: 'Uložit' })// → { type: Button, props: { color: 'blue', children: 'Uložit' } }
Komponenta vrací právě takový popis. Nic sama nekreslí – vrátí „objednávku na obrazovku“ a vykreslení provede React. Typická první komponenta – definice, kterou napíšeš jednou (třeba do souboru Welcome.tsx):
// ① DEFINICE komponenty (recept)function Welcome() {const name = 'Jana'const hour = new Date().getHours()return (<section className="welcome"><h1>Ahoj, {name}!</h1><p>{hour < 12 ? 'Dobré ráno' : 'Dobrý den'}</p></section>)}
Definice sama nic neukáže. Komponentu je potřeba použít – v JSX jiné komponenty se zapíše jako tag. Tady ji použije hlavní komponenta App:
// ② POUŽITÍ komponenty – v JSX jiné komponenty se píše jako tagfunction App() {return (<main><Welcome /></main>)}
Projdeme si, co je na ní důležité:
<welcome /> by považoval za neznámý HTML prvek.{…} může být libovolný výraz – proměnná, výpočet, volání funkce, ternár. Příkazy jako if nebo for tam ale nejdou; ty patří nad return.<>…</>.className místo class, htmlFor místo for, události v camelCase (onClick) a styly jako objekt (style={{ color: 'red' }}).Komponenta Welcome výše umí pozdravit jen Janu. To je jako recept „omeleta pro Janu“. Chceme recept, který pozdraví kohokoli – jméno mu musí přijít zvenku. K tomu slouží props.
Props jsou prostě argumenty funkce. Všechny atributy, které napíšeš na tag komponenty, React posbírá do jednoho objektu a předá ho jako první parametr.
Kód s props má vždycky dvě části na dvou různých místech: ① recept – definici komponenty, kterou napíšeš jednou (většinou do vlastního souboru), a ② objednávku – místo, kde komponentu použiješ v JSX jiné komponenty. Nejdřív recept:
// ① RECEPT – definice komponenty, třeba v souboru ProfileCard.tsx// Jaké props komponenta přijímá (tvar objednávky):interface ProfileCardProps {name: stringrole?: string // otazník = nepovinnýavatar?: string}// ↓ sem React dosadí atributy, které napíšeš na tag v ②function ProfileCard({ name, role = 'Vývojář', avatar = '🙂' }: ProfileCardProps) {return (<div className="card"><span>{avatar}</span><strong>{name}</strong><div>{role}</div></div>)}
A tady je objednávka. Komponenta App chce ukázat dvě karty, tak do svého JSX napíše dvakrát tag <ProfileCard … /> – pokaždé s jinými atributy:
// ② OBJEDNÁVKA – použití komponenty v JSX jiné komponentyfunction App() {return (<div className="row">{/* Dvě různé objednávky, jeden recept: */}<ProfileCard name="Jana Nováková" role="Frontend" avatar="👩💻" /><ProfileCard name="Petr Svoboda" /></div>)}
Co z objednávky ② doputuje do receptu ①:
| Atribut na tagu ② | Proměnná v ProfileCard ① | U Petra (bez role a avatar) |
|---|---|---|
name="Jana Nováková" | name je 'Jana Nováková' | name je 'Petr Svoboda' |
role="Frontend" | role je 'Frontend' | role je 'Vývojář' (výchozí hodnota) |
avatar="👩💻" | avatar je '👩💻' | avatar je '🙂' (výchozí hodnota) |
Co se tu děje:
App posbírá atributy do objektu { name: 'Jana Nováková', role: 'Frontend', avatar: '👩💻' } a zavolá s ním funkci ProfileCard.name, role a avatar.role i avatar. Použijí se výchozí hodnoty zapsané za rovnítkem – stejně jako u obyčejné JS funkce.Některé recepty připraví jen obal a náplň nechají na hostovi. Pekař upeče tortillu a ty si řekneš, co do ní. Pekař nemusí znát všechny možné náplně – jen ví, jak tortillu zabalit.
V Reactu je tou náplní speciální prop children. Obsahuje všechno, co napíšeš mezi otevírací a zavírací tag komponenty. Komponenta pak jen rozhodne, kam ten obsah vloží.
// ① RECEPT – karta s nadpisem; náplň dostane v childrenfunction Card({ title, children }: { title: string; children: React.ReactNode }) {return (<div className="card"><h3>{title}</h3><div className="card-body">{children}</div></div>)}
A použití v jiné komponentě. Co je mezi <Card …> a </Card>, dostane Card jako children:
// ② OBJEDNÁVKY – stejná karta, pokaždé jiná náplňfunction App() {return (<div className="row"><Card title="Novinky"><p>Vyšel React 19!</p> {/* ← children první karty */}</Card><Card title="Tým"><ProfileCard name="Jana" /> {/* ← children druhé karty: */}<ProfileCard name="Petr" /> {/* obě karty z kapitoly 2 */}</Card></div>)}
Card neví nic o novinkách ani o týmu. Řeší jen rámeček a nadpis. Díky tomu ji můžeš použít kdekoli – tomuhle skládání se říká kompozice. Typ React.ReactNode znamená „cokoli, co jde vykreslit“: text, číslo, prvek, pole prvků, nebo nic.
Když komponenta potřebuje víc míst pro obsah (hlavička, tělo, patička), dostane další props typu ReactNode. Takovým místům se říká sloty. Třeba panel s tlačítky v hlavičce a patičkou – nejdřív jeho recept:
// ① RECEPT – panel se třemi sloty: actions, footer a childreninterface PanelProps {title: stringactions?: React.ReactNode // slot v hlavičce (nepovinný)footer?: React.ReactNode // slot v patičce (nepovinný)children: React.ReactNode // hlavní obsah}function Panel({ title, actions, footer, children }: PanelProps) {return (<section className="card"><header><strong>{title}</strong>{actions}</header><div>{children}</div>{footer !== undefined && <footer>{footer}</footer>}</section>)}
A objednávka – co se kam vloží, ukazují šipky:
// ② OBJEDNÁVKAfunction App() {return (<Paneltitle="Úkoly"actions={<button>+ Přidat</button>} // → {actions} v hlavičcefooter={<small>3 úkoly</small>} // → {footer} v patičce><ul> {/* → {children} uprostřed */}<li>Nakoupit</li><li>Uklidit</li><li>Zaplatit nájem</li></ul></Panel>)}
Co vidíš: Dvě karty vykreslené stejnou komponentou ProfileCard. První dostala všechny props a navíc obsah mezi tagy (children). Druhá dostala jen name.
Vyzkoušej:
children – komponenta ho jen vložila na připravené místo.{children !== undefined && …}. Díky němu druhá karta, která žádnou náplň nemá, nevykreslí ani prázdný obal.Co z toho plyne: Jeden recept, různé objednávky. Props mění údaje, children dodá celý kus obsahu, a komponenta nemusí vědět, co v něm je.
Načítám interaktivní část…
Recept často obsahuje podmínky: „když je host vegetarián, vynech šunku“, „když nemáme vejce, nabídni palačinky“. V JSX se podmínky píšou obyčejným JavaScriptem – React žádnou zvláštní syntaxi nemá. Jen si musíš vybrat správný zápis podle situace.
Typický příklad: ikonka schránky s počtem nových zpráv.
function Inbox({ user, count }: { user: User | null; count: number }) {// 1) Early return: když není přihlášený, zbytek receptu nás nezajímáif (!user) return <p>Přihlas se, prosím.</p>return (<div>{/* 2) && – buď něco, nebo nic */}{count > 0 && <span className="badge">{count}</span>}{/* 3) ternár – jedna ze dvou variant */}<p>{count > 0 ? 'Máš nové zprávy' : 'Nic nového'}</p></div>)}
| Zápis | Kdy použít |
|---|---|
if (!data) return … | Loading, chyby, prázdné stavy – komponenta skončí dřív. Nejčitelnější. |
cond && <X/> | Buď něco, nebo nic. Podmínka musí být boolean! |
cond ? <A/> : <B/> | Dvě varianty UI. |
const map = { a: <A/>, b: <B/> } | Víc variant podle hodnoty (místo řetězu ternárů). |
Proč ta poznámka „musí být boolean“? Zkusme místo count > 0 && … napsat kratší count && …:
{count && <span className="badge">{count}</span>}
&& v JavaScriptu nevrací true/false. Vrací levou stranu, pokud je „nepravdivá“, jinak pravou.count 3, levá strana je pravdivá → výsledek je <span>. V pořádku.count 0, levá strana je nepravdivá → výsledek je číslo 0.false, null a undefined nevykreslí. Ale čísla vykresluje – takže na stránce se objeví osamělá „0“.Řešení: dej do podmínky vždy skutečný boolean – count > 0, items.length > 0, !!value.
Co vidíš: Nahoře tři řádky, které zobrazují počet zpráv třemi způsoby: špatně (count &&), správně (count > 0 &&) a ternárem. Dole odznak uživatele napsaný s early return.
Vyzkoušej:
count 0. V řádku „Špatně“ uvidíš osamělou 0, řádek „Správně“ je prázdný.UserBadge při chybějícím uživateli skončí hned na prvním řádku a zbytek vůbec neřeší.Co z toho plyne: Do && patří jen boolean. Pro dvě varianty použij ternár, pro „nic nekresli / načítám / chyba“ early return.
Načítám interaktivní část…
Představ si šatnu v divadle. Odevzdáš kabát a dostaneš lísteček s číslem. Po představení podle čísla dostaneš zpátky svůj kabát – i kdyby šatnářka mezitím věšáky přeskládala.
Co kdyby šatna místo čísel fungovala podle pozice: „váš kabát je třetí zleva“? Dokud se nic nehne, funguje to. Jakmile ale někdo pověsí kabát na začátek řady, všechno se posune a ty odejdeš v cizím kabátě.
Přesně tohle řeší v Reactu key. Když vykreslíš seznam, React si mezi rendery musí pamatovat, který prvek je který – hlavně proto, že prvky můžou mít vlastní stav (text v inputu, rozbalený detail). key je ten lísteček s číslem.
// Data – pole úkolůconst tasks = [{ id: 17, label: 'Nakoupit' },{ id: 42, label: 'Uklidit' },]// ① RECEPT – jeden řádek seznamufunction TaskRow({ task }: { task: { id: number; label: string } }) {return <li>{task.label}</li>}// ② POUŽITÍ – seznam: map udělá z každého úkolu jeden <TaskRow>function TaskList() {return (<ul>{tasks.map((task) => (<TaskRow key={task.id} task={task} />))}</ul>)}
Co je v ukázce pevně dané a co sis pojmenoval sám? Pevná jsou jen dvě slova – key a map. Všechno ostatní můžeš pojmenovat po svém:
| Slovo | Kdo ho určuje | Co znamená |
|---|---|---|
key | React – musí se jmenovat přesně takhle | lísteček s číslem; React si ho vezme pro sebe a komponentě ho nepředá |
map | JavaScript – metoda pole | z každé položky pole udělá jeden prvek |
tasks | ty | pole s daty – klidně todos nebo items |
task | ty | jedna položka, na které je map právě teď (parametr callbacku) |
id, label | ty (nebo databáze) | vlastnosti položky; id se hodí jako klíč, protože je u každé jiné |
TaskRow | ty | komponenta pro jeden řádek – jen musí začínat velkým písmenem |
task={task} | ty | obyčejná prop: vlevo její jméno, vpravo proměnná, kterou do ní posíláš |
map: z pole dat uděláš pole prvků. (Jak map, filter a další metody polí fungují, vysvětluje lekce JavaScript 04.)key se píše na prvek, který map vrací. Musí být jedinečný mezi sourozenci a stabilní – stejná položka musí mít při každém renderu stejný klíč.Řekněme, že každý řádek má vlastní input na poznámku. Do řádku „Nakoupit“ napíšeš „mléko“. Pak přidáš nový úkol „Vyvenčit psa“ na začátek. React porovnává staré a nové řádky podle klíče:
| Klíč | Před přidáním | Po přidání | Kde je poznámka „mléko“ |
|---|---|---|---|
key={index} | 0 = Nakoupit, 1 = Uklidit | 0 = Vyvenčit psa, 1 = Nakoupit, 2 = Uklidit | u „Vyvenčit psa“ – řádek 0 si nechal svůj stav |
key={task.id} | 17 = Nakoupit, 42 = Uklidit | 99 = Vyvenčit psa, 17 = Nakoupit, 42 = Uklidit | u „Nakoupit“ – řádek 17 se jen posunul |
S indexem si React myslí, že řádek 0 je pořád tentýž řádek, jen dostal jiný text. Ponechá mu tedy jeho stav – poznámku „mléko“. S ID ví, že řádek 99 je nový, a řádky 17 a 42 jen přesune i s jejich stavem.
Co vidíš: Stejný seznam úkolů vykreslený dvakrát. Vlevo s key={index}, vpravo s key={task.id}. Každý řádek má vlastní input – jeho text je stav řádku. Tlačítko přidá nový úkol na začátek obou seznamů.
Vyzkoušej:
Co z toho plyne: Stav patří prvku s daným klíčem. Klíč podle pozice přiřadí stav špatnému prvku, jakmile se pořadí změní.
Načítám interaktivní část…
Komponenta už props dostává – jen je ještě nevypisuje.
Zadání najdeš v komentářích v kódu níže – na začátku souboru a u míst označených TODO.
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…
Výzvy jdou v pořadí kapitol. První procvičí kapitolu 3 – napíšeš „obal“, který neví nic o svém obsahu. Druhá kapitoly 4 a 5 (podmínky, seznamy a klíče).
Napiš „tortillu“: panel s nadpisem, který přijme libovolnou náplň přes children a volitelné sloty pro tlačítka a patičku. Tenhle vzor uvidíš v každé UI knihovně. Co se má na konci ukázat, je v komentářích v kódu.
Zadání najdeš v komentářích v kódu níže – na začátku souboru a u míst označených TODO.
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…
Vykresli seznam produktů s cenou, vyprodané zvýrazni a přidej filtr „Jen skladem“. Checkbox už je připravený (používá stav, který podrobně probereme v příští lekci). Co se má ukázat, je v komentářích v kódu.
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…
{…}.ReactNode) dovolí komponentě obalit obsah, o kterém nic neví.&& – ale do && jen boolean, jinak se vykreslí 0.products, Product)products, Product – ze souboru src/course/fakeApi.ts. Falešné API kurzu: data a funkce, které předstírají server (se zpožděním, občas i s chybou). Ve skutečné aplikaci by tu bylo volání fetch().export interface Product {id: numbername: stringcategory: 'ovoce' | 'zelenina' | 'pečivo' | 'mléčné'price: numberinStock: boolean}export const products: Product[] = [{ id: 1, name: 'Jablko', category: 'ovoce', price: 12, inStock: true },{ id: 2, name: 'Banán', category: 'ovoce', price: 8, inStock: true },{ id: 3, name: 'Hruška', category: 'ovoce', price: 15, inStock: false },{ id: 4, name: 'Mrkev', category: 'zelenina', price: 6, inStock: true },{ id: 5, name: 'Rajče', category: 'zelenina', price: 9, inStock: true },{ id: 6, name: 'Okurka', category: 'zelenina', price: 19, inStock: false },{ id: 7, name: 'Rohlík', category: 'pečivo', price: 3, inStock: true },{ id: 8, name: 'Chléb', category: 'pečivo', price: 45, inStock: true },{ id: 9, name: 'Mléko', category: 'mléčné', price: 22, inStock: true },{ id: 10, name: 'Sýr eidam', category: 'mléčné', price: 39, inStock: true },{ id: 11, name: 'Jogurt', category: 'mléčné', price: 14, inStock: false },{ id: 12, name: 'Pomeranč', category: 'ovoce', price: 11, inStock: true },]
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).