Lekce 1 · Základy
Rychlé opakování základů
JSX, komponenty, props, children, podmínky, seznamy a proč na key záleží.
Načítám lekci…
Lekce 1 · 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. Ve složených závorkách { } pak můžeš do JSX vložit hodnotu z JavaScriptu:
function Welcome({ name }) {return (<section className="welcome"><h1>Ahoj, {name}!</h1></section>)}// Jeden recept, dvě objednávky → dvě jídla<Welcome name="Jana" /><Welcome name="Petr" />
<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:
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>)}// Použití – komponenta se píše jako tag:<Welcome />
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.
interface ProfileCardProps {name: stringrole?: string // otazník = nepovinnýavatar?: string}function ProfileCard({ name, role = 'Vývojář', avatar = '🙂' }: ProfileCardProps) {return (<div className="card"><span>{avatar}</span><strong>{name}</strong><div>{role}</div></div>)}// Dvě různé objednávky, jeden recept:<ProfileCard name="Jana Nováková" role="Frontend" avatar="👩💻" /><ProfileCard name="Petr Svoboda" />
Co se tu děje:
{ name: 'Jana Nováková', role: 'Frontend', avatar: '👩💻' } a zavolá s ním 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ží.
function Card({ title, children }: { title: string; children: React.ReactNode }) {return (<div className="card"><h3>{title}</h3><div className="card-body">{children}</div></div>)}// Stejná karta, pokaždé jiná náplň:<Card title="Novinky"><p>Vyšel React 19!</p></Card><Card title="Tým"><ProfileCard name="Jana" /><ProfileCard name="Petr" /></Card>
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), předáš další props typu ReactNode. Takovým místům se říká sloty:
<Paneltitle="Úkoly"actions={<button>+ Přidat</button>} // slot v hlavičcefooter={<small>3 úkoly</small>} // slot v patičce><TaskList /> // children = hlavní obsah</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 && …}. 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.
const tasks = [{ id: 17, label: 'Nakoupit' },{ id: 42, label: 'Uklidit' },]<ul>{tasks.map((task) => (<TaskRow key={task.id} task={task} />))}</ul>
map: z pole dat uděláš pole prvků. (Jak map, filter a další metody polí fungují, vysvětluje lekce 0.4.)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.
VÝZVA: Jmenovka
Komponenta Badge dostává props `name` a `role`, ale zatím ukazuje otazníky.
1. Místo „?“ v <strong> vykresli jméno: {name}
2. Místo „?“ pod ním vykresli roli: {role}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ě.
VÝZVA: Znovupoužitelný Panel (kompozice přes children a „sloty“)
Komponenta <Panel> má hotový vzhled i typy props. Chybí jen to, aby props opravdu používala.
1. V Panelu vykresli `title` do <strong> v hlavičce.
2. Vpravo v hlavičce vykresli slot `actions` (může chybět – pak se nevykreslí nic).
3. Do těla panelu vykresli `children`.
4. Patičku <footer> vykresli JEN když `footer` existuje.
5. V Dashboardu nahraď tři <div>y komponentou <Panel>:
a) „Tržby“ – obsah `revenue`, footer „Aktualizováno dnes“
b) „Úkoly“ – obsah `taskList`, actions `addButton`
c) „Poznámka“ – jen text, bez actions a footer
Obsah (revenue, taskList, addButton) už je připravený v proměnných.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). Podrobné zadání je hned pod tímto textem.
VÝZVA: Seznam produktů
Data: pole `products` je importované nahoře ze src/course/fakeApi.ts.
Každý produkt vypadá takto: { id: 1, name: 'Jablko', category: 'ovoce', price: 12, inStock: true }
Komponenta ProductItem (jeden řádek seznamu) i checkbox jsou připravené.
1. Vypočítej `visible`: když je zaškrtnuto „Jen skladem“, jen produkty s inStock === true, jinak všechny.
2. Vykresli `visible` přes .map() jako <ProductItem> (nezapomeň na správný `key`).
3. V ProductItem: vyprodaný produkt dostane třídu "muted" a za názvem text „(vyprodáno)“.
4. Pod seznamem doplň čísla do „Zobrazeno X z Y produktů“.
5. Bonus: seskup produkty podle kategorie (nadpis <h5> + seznam pro každou).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).