Přeskočit na obsah
Kurz Reactu1.0.0 beta
CSEN
Kurz Reactu1.0.0 beta
CSEN
🏠 Úvod🧭 Kde začít?🔁 Opakování📄 Taháky📰 Co je novéhoℹ️ O kurzu
JavaScript0/22
TypeScript0/6

Základy

  • 01 Rychlé opakování základů0/3
  • 02 Stav a události (useState)0/3

Hooky do hloubky

  • 03 Efekty (useEffect)0/3
  • 04 Refy (useRef)0/3
  • 05 Složitější stav (useReducer)0/3
  • 06 Context0/3
  • 07 Vlastní hooky0/3

Formuláře

  • 08 Formuláře a React 19 Actions0/3

Výkon

  • 09 Výkon a renderování0/3

Vzory

  • 10 Návrhové vzory komponent0/3

Ekosystém

  • 11 TypeScript s Reactem0/3
  • 12 Routování (React Router)0/3
  • 13 Načítání dat0/3
  • 14 Správa stavu aplikace0/3

Projekt

  • 15 Závěrečný projekt: Kanban0/2

Shrnutí

  • 16 Shrnutí: principy Reactu

Tvůj účet

Ochrana osobních údajů

React 11 · Ekosystém

TypeScript s Reactem

Typování props, událostí, hooků, generické komponenty a discriminated unions.

Načítám lekci…

💬 Našel jsi chybu, něco ti není jasné, nebo máš nápad? Napiš mi.
← Návrhové vzory komponentRoutování (React Router) →

Než začneme: zásuvky a zástrčky

Do české zásuvky nestrčíš britskou zástrčku. Nemusíš nic zapojovat ani zkoušet, jestli to vyhodí pojistky – je to poznat už podle tvaru. Tvar zásuvky je vlastně smlouva: „sem patří tohle, a nic jiného“.

Typ props je přesně takový tvar zásuvky. Komponenta řekne: „potřebuju jméno (text), roli (buď admin, nebo user) a funkci, kterou zavolám po kliknutí“. Kdo ji použije s chybějícím jménem nebo s rolí „admn“, dozví se to v editoru, ještě před spuštěním – červenou vlnovkou, ne chybou v prohlížeči u zákazníka.

V lekci TypeScript 01 jsme se naučili, kam se štítky s typy lepí. Teď je nalepíme na věci, které jsou typické pro React: props, události, hooky, kontext a komponenty, které pracují s libovolnými daty.

💡 Dokumentace, která nelže
Editor ti napoví props, odhalí překlep v názvu akce reduceru, upozorní na null, který jsi neošetřil, a přejmenování propu v padesáti souborech je bezpečné. Celý tento kurz je v TypeScriptu – podívej se na kód libovolné ukázky.
ℹ️ Co tě v lekci čeká
  1. Typování props a typy, které nabízí React.
  2. Rozšíření nativních prvků a props „buď–anebo“.
  3. Události.
  4. Hooky a kontext.
  5. Generické komponenty: tabulka pro libovolná data.
  6. Užitečné triky.

1. Props: tvar zásuvky

Karta uživatele, která ukazuje jméno, roli a po kliknutí dá vědět rodiči:

interface UserCardProps {
id: number
name: string
age?: number // nepovinné
role: 'admin' | 'user' // jen jedna z těchto dvou hodnot
onSelect: (id: number) => void // funkce, kterou karta zavolá
children?: ReactNode // obsah mezi tagy
}
function UserCard({ id, name, age = 18, role, onSelect, children }: UserCardProps) {
return (
<div onClick={() => onSelect(id)}>
<strong>{name}</strong> ({age}) – {role}
{children}
</div>
)
}

Co za nás TypeScript ohlídá:

<UserCard id={1} name="Jana" role="admin" onSelect={select} /> // ✅
<UserCard id={1} role="admin" onSelect={select} />
// ❌ Property 'name' is missing – chybí povinný prop
<UserCard id={1} name="Jana" role="admn" onSelect={select} />
// ❌ Type '"admn"' is not assignable to type '"admin" | "user"' – překlep
<UserCard id={1} name="Jana" role="user" onSelect={(id: string) => …} />
// ❌ onSelect dostane number, ne string
  • Unie textových hodnot ('admin' | 'user') místo obecného string. Editor ti při psaní nabídne obě možnosti a překlep nepustí.
  • Nepovinný prop s výchozí hodnotou: otazník v typu, rovnítko v destrukturaci. Uvnitř komponenty je age vždy číslo.
  • Callback se popisuje jako typ funkce: co dostane a co vrátí (void = nic užitečného).

React nabízí několik vlastních typů, které se v props hodí:

TypKdy
ReactNodechildren a sloty – text, číslo, JSX, pole, null… cokoli vykreslitelného.
ReactElementKdyž potřebuješ právě jeden element (vzácné).
ComponentProps<'button'>Všechny props nativního elementu.
ComponentProps<typeof MyComp>Props existující komponenty (i z knihovny, která je neexportuje).
CSSPropertiesObjekt pro prop style.
ℹ️ interface nebo type?
Obojí funguje. Konvence: interface pro tvary objektů (props), type pro unie a kompozice (A & B, 'a' | 'b'). Hlavně buď konzistentní.

2. Rozšíření nativních prvků a props „buď–anebo“

Vlastní tlačítko se vším, co umí button

Píšeš vlastní Button s variantami barev. Uživatel ale bude chtít předat i disabled, type="submit", aria-label, title… Vypisovat je všechny ručně je nesmysl. Vezmeš proto tvar nativní zásuvky a přidáš k němu svůj:

type ButtonProps = ComponentProps<'button'> & {
variant?: 'primary' | 'ghost' | 'danger'
}
function Button({ variant = 'primary', className = '', ...rest }: ButtonProps) {
return <button className={`btn btn-${variant} ${className}`} {...rest} />
}
  1. ComponentProps<'button'> = všechny props, které umí obyčejný <button>.
  2. & = „a zároveň“. Výsledek má nativní props i naši variantu.
  3. ...rest posbírá všechno, co jsme si nevzali, a pošle to dál na <button>. Včetně onClick, disabled i ref.
❓ Tři tečky v ({ variant, ...rest }) a v {...rest} – je to totéž?

Stejný zápis, dva opačné směry:

  • V parametru (rest): ({ variant, className, ...rest }) = „vytáhni variant a className, a všechno ostatní zabal do objektu rest“. Sbírá.
  • V JSX nebo objektu (spread): <button {...rest} /> = „rozbal objekt rest a každou jeho vlastnost předej jako samostatný atribut“. Rozhazuje.

Dohromady to znamená: co si komponenta vzala, zpracuje sama, zbytek pošle beze změny dál. Pozor na pořadí: atribut napsaný za {...rest} ho přepíše, atribut napsaný před ním může být přepsán.

Props „buď–anebo“: discriminated union

Na poště vyplňuješ formulář. Nahoře zaškrtneš, jestli posíláš dopis, nebo balík – a podle toho vyplníš jiná políčka. U balíku váhu a rozměry, u dopisu nic z toho. Kdo zaškrtne dopis a vyplní váhu, něco pokazil.

Jak discriminated union funguje a jak ho TypeScript zúží, vysvětluje lekce TypeScript 02. Tady ho použijeme na props.

Komponenta, která je buď odkaz, nebo tlačítko s akcí, má stejný problém. S obyčejnými nepovinnými props jde zapsat nesmysl:

// ❌ Nepovinné obojí → jde předat obojí, nebo nic
interface Props { href?: string; onClick?: () => void; children: ReactNode }
// ✅ „Zaškrtávací políčko“ kind rozhodne, co dalšího je povinné
type LinkOrActionProps =
| { kind: 'link'; href: string; children: ReactNode }
| { kind: 'action'; onClick: () => void; children: ReactNode }
function LinkOrAction(props: LinkOrActionProps) {
if (props.kind === 'link') {
return <a href={props.href}>{props.children}</a> // TS ví: tady je href
}
return <button onClick={props.onClick}>{props.children}</button> // a tady onClick
}

Uvnitř if TypeScript ví, kterou variantu máš, a pustí tě jen k jejím vlastnostem. A zvenku nejde napsat kind="link" bez href.

Ukázka

Button s nativními props + LinkOrAction

Co vidíš: Nahoře tři tlačítka vlastní komponenty Button – s ikonou, vypnuté s nativním title, a jen s ikonou a aria-label. Dole dvě varianty LinkOrAction.

Vyzkoušej:

  1. Najeď myší na vypnuté tlačítko Disabled. Ukáže se nápověda z atributu title – prošel přes ...rest, aniž bychom ho museli vypisovat.
  2. Klikni na Uložit a otevři konzoli (F12). Handler dostal správně otypovanou událost tlačítka.
  3. Klikni na Odkaz (vede na tuhle lekci) a na Akce (ukáže hlášku). Stejná komponenta, dvě varianty.
  4. Otevři kód a najdi zakomentovaný řádek s kind="link" a onClick. Kdybys ho odkomentoval, TypeScript ohlásí chybu.

Co z toho plyne: Nativní props zdědíš jedním řádkem. Discriminated union zaručí, že komponenta dostane vždy smysluplnou kombinaci.

Načítám interaktivní část…

3. Události

Když napíšeš handler přímo do JSX, TypeScript ví, ke kterému prvku a události patří, a typ e odvodí sám. Když handler vytáhneš do samostatné proměnné, tuhle souvislost ztratí – a typ musíš napsat. Je to neprůhledná krabice z lekce TypeScript 01.

// Inline handler: typ se odvodí sám – nejjednodušší varianta
<input onChange={(e) => setName(e.target.value)} />
// Samostatný handler: typ události + typ prvku ve špičatých závorkách
const handleChange = (e: ChangeEvent<HTMLInputElement>) => setName(e.target.value)
const handleSubmit = (e: FormEvent<HTMLFormElement>) => { e.preventDefault() }
const handleKey = (e: KeyboardEvent<HTMLInputElement>) => { if (e.key === 'Enter') … }
const handleClick = (e: MouseEvent<HTMLButtonElement>) => { e.currentTarget.disabled = true }
// Nebo typ celého handleru najednou
const onClick: MouseEventHandler<HTMLButtonElement> = (e) => { … }
✨ Trik: nech si typ poradit
Nevíš typ? Napiš handler nejdřív inline, najeď myší na e a editor ti ho řekne. Pak handler vytáhni a typ zkopíruj.

4. Hooky a kontext

U hooků platí pravidlo průhledných krabic: když TypeScript vidí počáteční hodnotu a je z ní všechno jasné, typ nepiš. Když je krabice prázdná nebo „zatím nic“, typ předej ve špičatých závorkách.

useState(0) // number – průhledná krabice
useState<User | null>(null) // teď nic, později User – musíš uvést
useState<Product[]>([]) // prázdné pole – musíš uvést (jinak never[])
useRef<HTMLInputElement>(null) // ref na prvek stránky
useRef<number | null>(null) // šuplík na ID časovače
useRef(0) // šuplík s číslem – odvozeno
useReducer(reducer, initial) // typy se odvodí z reduceru
createContext<AuthValue | null>(null) // + vlastní hook s kontrolou null
// Vlastní hook vracející dvojici
function useToggle() {
…
return [on, toggle] as const // readonly [boolean, () => void]
}

U useReducer stačí pořádně otypovat reducer – stav a akce jako discriminated union z lekce o reduceru. Všechno ostatní (typ state, povolené akce v dispatch) se z něj odvodí.

❓ Jak funguje kontrola úplnosti přes never (výzva TypedCart)?

Stejně jako v lekci TypeScript 02 („Kontrola úplnosti s never“). Připomenutí: never je typ, do kterého nejde přiřadit nic. V reduceru se to využije ve switch nad akcemi:

switch (action.type) {
case 'added': …
case 'removed': …
case 'cleared': …
default: {
const unreachable: never = action // sem se smí dostat jen „nic“
throw new Error('Neznámá akce')
}
}

V každém case TypeScript odškrtne jednu variantu akce. Když jsou obsloužené všechny, v default už žádná nezbyla, action má typ never a přiřazení projde. Když přidáš do Action novou variantu a zapomeneš na ni, zbude v default a TypeScript zahlásí, že ji nejde přiřadit do never. Zapomenutou akci tak najdeš v editoru, ne v aplikaci.

5. Generické komponenty

Prodejní automat neví předem, co v něm bude. Když ho naplníš plechovkami, vydává plechovky; když sušenkami, sušenky. A nevydá ti sušenku z automatu s plechovkami. Generická komponenta funguje stejně: neví předem, s jakými daty bude pracovat, ale jakmile jí je dáš, „naučí se“ jejich typ a hlídá ho.

Typický příklad: tabulka, která umí zobrazit produkty, uživatele, objednávky…

interface Column<T> {
key: keyof T & string // jen skutečná vlastnost T
header: string
render?: (row: T) => ReactNode // řádek přijde správně otypovaný
}
interface TableProps<T> {
rows: T[]
columns: Column<T>[]
}
function Table<T>({ rows, columns }: TableProps<T>) { … }
  1. <T> za názvem funkce říká: „tahle komponenta má parametr typu, zatím ho neznám“.
  2. Když napíšeš <Table rows={products} … />, TypeScript se podívá na rows a zjistí, že T je Product. Automat je naplněný.
  3. Od té chvíle musí každý key ve sloupcích být vlastnost produktu ('name', 'price'…) a funkce render dostane Product.
<Table
rows={products}
columns={[
{ key: 'name', header: 'Název' },
{ key: 'price', header: 'Cena', render: (p) => `${p.price} Kč` }, // p je Product
{ key: 'color', header: 'Barva' }, // ❌ 'color' není vlastnost Product
]}
/>
Ukázka

Generická tabulka s řazením

Co vidíš: Jedna komponenta Table použitá dvakrát: s produkty a s uživateli. Sloupce s šipkou jdou řadit.

Vyzkoušej:

  1. Klikni na záhlaví Cena u produktů, pak znovu. Řadí se vzestupně a sestupně.
  2. Klikni na Město u uživatelů. Stejný kód řazení, jiná data.
  3. Otevři kód a najdi zakomentovaný sloupec color. Produkt žádnou barvu nemá – kdybys řádek odkomentoval, TypeScript ho podtrhne.

Co z toho plyne: Komponenta je napsaná jednou pro libovolná data. Typ dat se odvodí z rows a hlídá sloupce i funkce render.

Načítám interaktivní část…

⚠️ Past při odvozování generik
<Select options={[1, 2]} value="a" /> TypeScript nezakáže – z options i value odvodí T = number | string. Když chceš, aby se T odvozovalo jen z options, použij value: NoInfer<T> (TS 5.4+).

6. Užitečné triky

ZápisK čemu
const sizes = ['S', 'M'] as constReadonly dvojice textů → typeof sizes[number] = "S" | "M".
obj satisfies Record<Tone, string>Ověří tvar, ale zachová přesný typ hodnoty.
Omit<ButtonProps, "type">Props bez některých klíčů.
Partial<T>, Required<T>, Pick<T, K>Odvozené typy bez kopírování.
const x: never = actionKontrola úplnosti ve switchi.
z.infer<typeof schema>Typ ze Zod schématu – validace i typy z jednoho zdroje.
❓ Jaký je rozdíl mezi const x: Typ = …, … as Typ a … satisfies Typ?
type Tone = 'info' | 'danger'
const a: Record<Tone, string> = { info: 'blue', danger: 'red' }
// „a JE typu Record“ – zkontroluje a typ proměnné bude přesně Record<Tone, string>
const b = { info: 'blue', danger: 'red' } satisfies Record<Tone, string>
// „zkontroluj, že to SPLŇUJE Record“ – ale typ b zůstane přesný: { info: string; danger: string }
const c = { info: 'blue' } as Record<Tone, string>
// „věř mi, je to Record“ – NEKONTROLUJE, že chybí danger. Lež projde.

: Typ a satisfies obě kontrolují. Liší se tím, jaký typ pak proměnná má: satisfies zachová ten přesnější, odvozený. as nekontroluje nic, jen přikáže. Používej ho jen tam, kde opravdu víš víc než TypeScript (třeba e.target as Node).

Ověř si to

Kvíz Jaký typ má items po const [items, setItems] = useState([])?
Kvíz Jak typovat prop, do kterého chceš vložit <Icon /> nebo text nebo nic?
Kvíz Komponenta Notice má buď prop message (text), nebo prop error (objekt Error) – nikdy obojí. Jak to popsat?

Rozcvička

Výzva

Štítek s typovanými props

Rozcvička

Typ props je napsaný – komponenta ho jen musí použít.

Zadání najdeš v komentářích v kódu níže – na začátku souboru a u míst označených TODO.

🧩 Odkud se berou věci, které tu nejsou definované
CSS třídy jako 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

První výzva procvičí kapitoly 1, 2 a 5: tvary zásuvek pro props, rozšíření nativního tlačítka a generický Select. Druhá kapitolu 4 – typovaný reducer a kontext, ve kterém překlep v názvu akce odhalíš dřív, než uložíš soubor.

✨ Automatická kontrola
Tyto výzvy mají typové testy. V terminálu spusť npm run typecheck:challenges – dokud máš v kódu any, test selže. Když jsou typy správně, projde bez chyb.
Výzva

Vyžeň všechna any

StředníBonus · zdarma po přihlášení

UI vypadá stejně – změna je jen v typech. Úspěch pozná až TypeScript. Jaké typy mají jednotlivé props, je v komentářích v kódu.

🔓 Bonusová výzva – zdarma po přihlášení

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.

Jak nakládáme s tvými údaji
Výzva

Typově bezpečný košík

TěžkáBonus · zdarma po přihlášení

Discriminated union pro akce + typovaný kontext = překlep v názvu akce odhalíš dřív, než uložíš soubor. Co mají typy hlídat, je v komentářích v kódu.

🔓 Bonusová výzva – zdarma po přihlášení

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.

Jak nakládáme s tvými údaji

Shrnutí

  • Typ props je tvar zásuvky: špatná zástrčka se odhalí v editoru, ne u zákazníka. Unie textů místo obecného string.
  • ComponentProps<'button'> & {…} pro rozšíření nativních prvků, ReactNode pro children.
  • Discriminated union pro props „buď–anebo“ (i pro stav a akce) – nesmyslná kombinace nejde zapsat.
  • Události: inline se typ odvodí, samostatný handler potřebuje ChangeEvent<…> apod.
  • Hooky: useState<T>() u null, unií a prázdných polí; jinak nech TS odvodit.
  • Generické komponenty jsou automaty: typ dat se odvodí z props a hlídá zbytek.