Lekce 11 · Ekosystém
Kam napsat typ (syntaxe TypeScriptu)
Kdy dvojtečka, kdy <…>, kdy nic. Anatomie zápisu, tahák situací a doplňovačka.
Načítám lekci…
Lekce 11 · Ekosystém
Kdy dvojtečka, kdy <…>, kdy nic. Anatomie zápisu, tahák situací a doplňovačka.
Načítám lekci…
Když se stěhuješ, lepíš na krabice štítky: „kuchyň – sklo“, „knihy“, „zimní oblečení“. Krabicím samotným je štítek jedno – věci v nich jsou pořád stejné. Ale stěhováci podle štítků poznají, co se nesmí pokládat pod těžké knihy a kam co odnést. A až je všechno vybalené, štítky strhneš a vyhodíš.
TypeScript jsou štítky na JavaScriptu. Lepíš je na proměnné, parametry a vlastnosti: „tady je číslo“, „tady je uživatel nebo nic“. Kód díky nim nefunguje jinak – ale editor a kompilátor podle nich poznají, když se pokusíš dát sklo pod knihy (předat text tam, kde má být číslo). A při kompilaci se všechny štítky strhnou: prohlížeč dostane čistý JavaScript.
Tahle lekce není o tom, jaké štítky existují, ale kam se lepí: kdy dvojtečka, kdy <…> a kdy nic. Když to máš v malíku, následující lekce o TypeScriptu v Reactu bude hračka.
Na průhlednou krabici štítek nepotřebuješ – je vidět, co v ní je. Stejně tak TypeScript u const count = 0 vidí nulu a sám ví, že jde o číslo. Tomu se říká odvození typu (inference). Štítek musíš nalepit tam, kde dovnitř vidět není: na prázdnou krabici (prázdné pole), na krabici, která zatím zeje prázdnotou (null), nebo na krabici, která teprve přijede (parametr funkce, data z API).
Druhá věc, kterou je dobré vědět: typy po kompilaci zmizí – štítky se strhnou. Když z kódu mentálně vymažeš všechny typy, musí zbýt platný JavaScript. To je nejlepší test, jestli je typ na správném místě.
Zkusme to na typické funkci:
function formatPrice(amount: number, currency?: string): string {return amount + ' ' + (currency ?? 'Kč')}
amount: number – parametr amount je typu číslo. Název, dvojtečka, typ.currency?: string – otazník patří k názvu: parametr je nepovinný. Uvnitř funkce může být undefined, proto ?? 'Kč'.): string – dvojtečka za závorkou s parametry je typ toho, co funkce vrací. Stejné pravidlo, jen „název“ je tentokrát celé volání funkce.Teď štítky strhneme:
function formatPrice(amount, currency) {return amount + ' ' + (currency ?? 'Kč')}
Zbyl platný JavaScript. Kdyby ne – třeba kdyby z ({ title: string }) zbylo něco, co dělá jinou věc – typ byl na špatném místě. K tomu se dostaneme v další kapitole.
Co vidíš: Několik typických řádků s typy. Každý kousek je obarvený: fialově typ, oranžově syntaxe TypeScriptu (dvojtečky, otazníky, závorky), normálně obyčejný JavaScript. Podtržené kousky mají vysvětlivku.
Vyzkoušej:
price, dvojtečku a number. Pod kódem se objeví, co který kousek znamená.Co z toho plyne: Typ je vždy štítek nalepený na konkrétní místo, nejčastěji za dvojtečku. Bez něj zůstane JavaScript, který dělá totéž.
Načítám interaktivní část…
Dvojtečka je v JavaScriptu i bez TypeScriptu – v objektech odděluje klíč a hodnotu, v destrukturaci přejmenovává. Proto je to nejčastější zdroj zmatku: stejný znak, tři různé významy. Pomáhá se zeptat: stojí dvojtečka za něčím, co právě deklaruju? Pak je to štítek.
| Místo | Zápis | Poznámka |
|---|---|---|
| Proměnná | let name: string | Jen když TS typ neodvodí z hodnoty. |
| Parametr | (name: string) => … | Vždy. |
| Nepovinný parametr / vlastnost | name?: string | Otazník patří k názvu. |
| Návratová hodnota | function f(): string | Za ), u arrow funkce před =>. |
| Vlastnost v interface / type | { name: string } | Popis tvaru objektu. |
| Props komponenty | ({ name }: Props) | Za celou destrukturaci! |
| Tady dvojtečka NENÍ | Zápis | Proč |
|---|---|---|
| Definice type | type Id = string | number | Pojmenováváš typ → rovnítko. |
| Definice interface | interface User { … } | Klíčové slovo + jméno + tělo. |
| Typ pro funkci/hook | useState<User | null>(null) | Typ se funkci PŘEDÁVÁ ve špičatých závorkách. |
| Objekt s hodnotami | { name: 'Jana' } | Tady dvojtečka odděluje klíč a HODNOTU. |
| JSX | <Card title="Ahoj" /> | V JSX žádné typy – TS je kontroluje podle props. |
| Destrukturace | { title: nadpis } | Dvojtečka tu znamená PŘEJMENOVÁNÍ proměnné. |
Zpátky ke krabicím. Štítek lepíš tam, kam není vidět, a na krabice, které přijíždějí zvenku. Na průhlednou krabici ho nelepíš – jen bys přidal práci a riziko, že štítek nebude odpovídat obsahu.
// Průhledné krabice – TS vidí dovnitř, štítek nepišconst count = 0 // numberconst names = ['Jana', 'Petr'] // string[]const [open, setOpen] = useState(false) // boolean// Krabice přijíždějí zvenku – štítek piš vždyfunction greet(name: string) { … } // parametr: kdo ví, co sem kdo pošleinterface CardProps { title: string } // props: totéž pro komponenty// Neprůhledné krabice – TS nevidí dovnitř, štítek dopišconst [user, setUser] = useState<User | null>(null) // teď null, později Userconst [todos, setTodos] = useState<Todo[]>([]) // prázdné pole – prvky čeho?
Jednoduché pravidlo: typy piš na hranicích – kde data vstupují (parametry, props, API) nebo kde TS nemá z čeho typ poznat. Uvnitř funkcí ho nech pracovat.
| ✅ Piš vždy | 🤔 Piš, když TS neodvodí | 🚫 Nepiš (zbytečné) |
|---|---|---|
| Parametry funkcí | useState<User | null>(null) | const x: number = 5 |
| Props komponent (interface) | useState<Todo[]>([]) | useState<number>(0) |
| Tvary dat (interface User) | useRef<HTMLDivElement>(null) | Parametry inline handlerů v JSX |
| Typ hodnoty kontextu | Samostatné event handlery | Parametry callbacků (.map(item => …)) |
| Veřejné funkce knihovny / utils | [object Object]přiřazení později | Návratový typ jednoduchých funkcí |
Tahák pro situace, které potkáš nejčastěji. Používej ho jako kuchařku: najdi, co právě píšeš, a podívej se, kam štítek patří.
Co vidíš: Tlačítka s typickými situacemi, rozdělená na obyčejný JavaScript a React. Ke každé situaci je správný zápis, nejčastější chyba a krátké vysvětlení.
Vyzkoušej:
{ title: string } uvnitř závorek – za chvíli ji uvidíš i v kvízu.Co z toho plyne: Většina situací se opakuje. Až si je párkrát najdeš v taháku, začneš štítky psát na správná místa automaticky.
Načítám interaktivní část…
| Symbol | Příklad | Význam |
|---|---|---|
: | age: number | „je typu“ |
? | age?: number | nepovinné (může chybět / být undefined) |
| | string | null | „nebo“ – union |
& | A & B | „a zároveň“ – spojení typů |
[] | string[] | pole |
[A, B] | [string, number] | tuple – pole s pevnou délkou |
=> | (id: number) => void | typ funkce |
<T> | Array<User> | generikum – typ jako parametr |
as | data as User | „věř mi“ – přetypování bez kontroly (šetři s ním) |
as const | ['a', 'b'] as const | nejpřesnější možný (readonly) typ |
! | ref.current! | „určitě není null“ – bez kontroly |
typeof | typeof config | typ z existující hodnoty |
keyof | keyof User | union názvů vlastností: "name" | "age" |
ChangeEvent) skočí na jeho definici.any nabídne „Infer parameter types from usage“.Doplň do políček, co tam patří – typ, symbol, nebo nic. Každé cvičení trénuje jedno místo, kam se štítek lepí. Pamatuj na průhledné krabice: někdy je správná odpověď nechat políčko prázdné.
function double(n) {
return n * 2
}function greet(name: string) {
return 'Ahoj ' + name
}function greet(name: string, excited boolean) {
return excited ? name + '!' : name
}interface User {
name string
age number // nepovinné
}interface User { name: string }
const jana: User = { name: }type Status 'idle' | 'loading' | 'error'
interface AvatarProps {
src: string
size?: number
}
function Avatar({ src, size = 40 }) {
return <img src={src} width={size} />
}interface ListProps {
items: string[]
onSelect: // dostane index vybrané položky
}const [user, setUser] = useState(null)
const [todos, setTodos] = useState([])
const [count, setCount] = useState(0)
const inputRef = useRef(null)
<input ref={inputRef} />const handleChange = (e) => {
setName(e.target.value)
}async function loadUser(id: number): {
const res = await fetch('/api/users/' + id)
return res.json()
}function first(items: T[]): T | undefined {
return items[0]
}function useToggle() {
const [on, setOn] = useState(false)
return [on, () => setOn(!on)]
}function Badge({ label: string, color: string }) {return <span style={{ color }}>{label}</span>}
setItems([...items, 'nový']) hlásí chybu?const [items, setItems] = useState([])
// Aconst handleClick: (e: MouseEvent<HTMLButtonElement>) => void = (e) => { … }// Bconst handleClick = (e: MouseEvent<HTMLButtonElement>) => { … }// Cconst handleClick = (e) : MouseEvent<HTMLButtonElement> => { … }
const Card = ({ title }) => <h2>{title}</h2>
Vyměň dvakrát any za skutečný typ a doplň, co funkce vracejí.
VÝZVA: Typy místo any Funkce mají u parametrů typ `any` a nic nevracejí. 1. formatPrice: změň `any` na `number` a vrať text s měnou: formatPrice(25) → '25 Kč' 2. isAdult: změň `any` na `number`, za závorku doplň návratový typ `: boolean` a vrať age >= 18 Testy kontrolují výsledky. Typy ověří `npm run typecheck`, když máš kurz stažený.
Načítám interaktivní část…
V obou výzvách je hotový kód plný any – krabic bez štítků. Tvým úkolem je štítky nalepit na správná místa: nejdřív u obyčejných funkcí, pak v React komponentě. Na stránce se nic nezmění; rozdíl uvidí jen TypeScript.
Sedm malých funkcí, sedm míst, kam typ patří. Výstup na stránce se nezmění – rozdíl uvidí jen TypeScript.
VÝZVA: Kam s typy? – obyčejné funkce
Každá funkce níže má `any`. Nahraď je správnými typy. Nejde o React – jde o to,
KAM se typ píše: za parametr, za závorku s parametry, do interface, do <T>.
1. interface Person: firstName (string), lastName (string), age (NEPOVINNÝ number).
Pak `export type Person = any` nahraď za `export interface Person { … }`.
2. formatPrice(amount, currency) – amount je number, currency je 'CZK' | 'EUR'
s výchozí hodnotou 'CZK'. Vrací string.
3. sum(numbers) – pole čísel → number.
4. parseAge(input) – string → number | null (null, když to není číslo).
5. fullName(person) – Person → string.
6. adults(people) – Person[] → Person[] (jen ti, kdo mají age >= 18).
7. findById(items, id) – GENERICKÁ: funguje pro jakékoli pole objektů s `id: number`
a vrací typ položky (nebo undefined). Tip: <T extends { id: number }>
✅ Ověř: npm run typecheck:challenges (soubor Utils.typetest.tsx)Načítám interaktivní část…
Všechna typická místa v React komponentě: props, callback, useState, useRef, event handler a vlastní hook.
Střední a těžké výzvy jsou bonus pro přihlášené. Registrace je zdarma – stačí e-mail, žádné heslo.
function f(): T.({ a, b }: Props). Uvnitř { } dvojtečka přejmenovává!type X = … (rovnítko), interface X { … }, typ pro hook ve špičatých závorkách: useState<T>().null, prázdná pole); průhledné krabice nech TS odvodit.Checks, Check)Checks, Check – ze souboru src/course/checks.tsx. Pomocník kurzu pro výzvy s funkcemi: spustí testy a ukáže ✅/❌. Jak funguje uvnitř, nemusíš řešit – stačí, že ti řekne, které kroky už máš hotové./** One test: a description and a function returning true (passed) or false. It may be async. */export type Check = [name: string, test: () => boolean | Promise<boolean>]/*** Runs the tests of a challenge and shows ✅ or ❌ for each.* An error thrown in a test (e.g. when trying to change a frozen object) is shown under the test.*/export function Checks({ tests }: { tests: Check[] }) {const t = useT()const [results, setResults] = useState<Result[]>(() => tests.map(() => ({ status: 'pending' })))useEffect(() => {let ignore = falsetests.forEach(([, test], i) => {Promise.resolve().then(test).then((ok): Result => (ok ? { status: 'ok' } : { status: 'fail' }),(e): Result => ({ status: 'fail', error: e instanceof Error ? e.message : String(e) }),).then((result) => {if (ignore) returnsetResults((prev) => prev.map((r, idx) => (idx === i ? result : r)))})})return () => {ignore = true}}, [tests])const passed = results.filter((r) => r.status === 'ok').lengthreturn (<div className="stack"><strong className={passed === tests.length ? 'ok' : undefined}>{t.checksPassed(passed, tests.length)}</strong><ul className="list">{tests.map(([name], i) => {const r = results[i] ?? { status: 'pending' }return (<li key={name}>{r.status === 'pending' ? '⏳' : r.status === 'ok' ? '✅' : '❌'} {name}{r.status === 'fail' && r.error && (<div className="bad muted">{t.checksError} {r.error}</div>)}</li>)})}</ul></div>)}
products)products – 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).