Proč React?
React (vytvořen Meta, 2013) je dnes de facto standard pro tvorbu komplexních webových aplikací. Nejedná se o framework — je to knihovna pro UI. Vše ostatní (routing, state management, fetching) si volíte sami.
| Vlastnost | Vanilla JS | React |
|---|---|---|
| UI aktualizace | Manuální DOM manipulace | Automaticky přes Virtual DOM |
| Struktura | Funkce + globální stav | Komponenty = self-contained jednotky |
| Škálovatelnost | Složité u velkých appek | Komponentová hierarchie škáluje dobře |
| Komunita | — | Největší JS ekosystém |
| Pracovní trh | — | Nejžádanější frontend skill |
Virtual DOM — jak React funguje
React nemanipuluje DOM přímo. Udržuje virtuální reprezentaci (VDOM) a při každém re-renderu porovná nový VDOM se starým (reconciliation — diffing). Pouze skutečně změněné části DOM pak aktualizuje. Výsledek: minimum DOM operací = rychlý UI.
JSX — JavaScript + XML
JSX je syntaktické rozšíření JavaScriptu — vypadá jako HTML, ale je to JS. Babel ho transpiluje na React.createElement() volání. Není povinný, ale prakticky vždy se používá.
// JSX se transpiluje na React.createElement()
const element = <h1 className="title">Ahoj světe</h1>;
// Je ekvivalentní s:
const element = React.createElement(
'h1',
{ className: 'title' },
'Ahoj světe'
);
// JSX pravidla:
// 1. Jeden root element (nebo React.Fragment / <></>)
// 2. class → className, for → htmlFor
// 3. Výrazy v {} — lze použít JS
// 4. CamelCase pro HTML atributy (onClick, onChange)
// 5. Samouzavírací tagy: <br />, <img />, <Input />
// JSX výrazy — JavaScript uvnitř {}
const jmeno = "Jana";
const cislo = 42;
const pole = ["a", "b", "c"];
const element = (
<>
<h1>Ahoj, {jmeno}!</h1>
<p>Číslo: {cislo * 2}</p>
<p>Datum: {new Date().getFullYear()}</p>
<p>{cislo > 10 ? "Velké" : "Malé"}</p>
<ul>{pole.map(x => <li key={x}>{x}</li>)}</ul>
{/* Komentáře takto */}
{podminka && <span>Jen pokud true</span>}
</>
);
Komponenty
Komponenta je funkce, která přijme props a vrátí JSX. React aplikace je strom komponent — každá zodpovídá za část UI.
// Funkční komponenta — základní forma
function Button({ label, onClick, variant = "primary" }) {
return (
<button
onClick={onClick}
className={`btn btn-${variant}`}
>
{label}
</button>
);
}
// Pojmenování: vždy PascalCase (Button, ne button)
// Proč? React rozlišuje lowercase (nativní HTML) od PascalCase (komponenty)
// Kompozice — komponenty uvnitř komponent
function Card({ title, children }) {
return (
<div className="card">
<h3>{title}</h3>
<div className="card-body">{children}</div>
</div>
);
}
// Použití:
<Card title="Moje karta">
<p>Libovolný obsah jako children</p>
<Button label="Klikni" onClick={() => alert('!')} />
</Card>
// Kvíz: Proč musí být název React komponenty PascalCase (velké první písmeno)?
Props — data shora dolů
Props (properties) jsou read-only data předaná z rodiče do dítěte. Data tečou jedním směrem — shora dolů. Dítě nikdy nemění props rodiče (one-way data flow).
// Props jsou read-only!
function Component({ name }) {
// name = "Jan"; // ❌ CHYBA — props nelze měnit
return <h1>{name}</h1>;
}
// Destrukturování props (doporučeno)
function Button({ label, onClick, disabled = false, size = "md" }) { }
// Celý objekt props (méně časté)
function Button(props) {
return <button onClick={props.onClick}>{props.label}</button>;
}
// Spread props — přeposlání dál
function Input({ label, ...rest }) {
return (
<div>
<label>{label}</label>
<input {...rest} /> {/* předá všechny ostatní props */}
</div>
);
}
// children — speciální prop pro vnoření
function Card({ title, children }) {
return (
<div>
<h2>{title}</h2>
{children}
</div>
);
}
// Validace props s TypeScript
interface ButtonProps {
label: string;
onClick?: () => void;
variant?: "primary" | "secondary" | "danger";
disabled?: boolean;
}
useState — reaktivní stav
useState je nejzákladnější React hook. Když se stav změní, React automaticky re-renderuje komponentu s novou hodnotou.
import { useState } from 'react';
function Counter() {
// [aktuální hodnota, funkce pro změnu]
const [count, setCount] = useState(0); // 0 = výchozí hodnota
return (
<div>
<p>Počet: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
// ✅ Funkční update — pro stav závislý na předchozím
setCount(prev => prev + 1); // bezpečnější v async kontextech
// ✅ Objekt jako stav
const [user, setUser] = useState({ name: 'Jana', age: 30 });
// Vždy merge ručně! setState PŘEPÍŠE celý objekt
setUser(prev => ({ ...prev, age: 31 }));
// ✅ Pole jako stav
const [items, setItems] = useState([]);
// Přidání
setItems(prev => [...prev, novyItem]);
// Smazání
setItems(prev => prev.filter(item => item.id !== id));
// Aktualizace
setItems(prev => prev.map(item =>
item.id === id ? { ...item, done: true } : item
));
// Kvíz: Máte useState({ name: 'Jana', age: 30 }). Chcete aktualizovat pouze age. Jak?
Event handlers
// Předáváme funkci, ne volání!
<button onClick={handleClick}> {/* ✅ */}
<button onClick={handleClick()}> {/* ❌ zavolá se ihned! */}
<button onClick={() => handleClick()}> {/* ✅ arrow funkce */}
// Syntetické eventy — React wrapper nad nativními
function Form() {
const handleSubmit = (e) => {
e.preventDefault(); // zabrání reload stránky
const data = new FormData(e.target);
console.log(Object.fromEntries(data));
};
const handleChange = (e) => {
console.log(e.target.name, e.target.value);
};
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" onChange={handleChange} />
<button type="submit">Odeslat</button>
</form>
);
}
// Předání dat do handleru
{items.map(item => (
<button key={item.id} onClick={() => handleDelete(item.id)}>
Smazat {item.name}
</button>
))}
Listy a key prop
Renderování pole v React probíhá přes .map(). Každý element musí mít unikátní key — React ho používá při reconciliation k identifikaci položek.
const produkty = [
{ id: 1, nazev: "Nůž A", cena: 299 },
{ id: 2, nazev: "Nůž B", cena: 499 },
];
// ✅ Správně — key z unikátního ID
{produkty.map(p => (
<ProductCard key={p.id} {...p} />
))}
// ❌ Index jako key — problém při mazání/přesouvání
{produkty.map((p, index) => (
<ProductCard key={index} {...p} /> // NEPOUŽÍVAT pokud lze
))}
// Filtrování a řazení
const filtered = produkty
.filter(p => p.cena < 400)
.sort((a, b) => a.nazev.localeCompare(b.nazev));
// Podmíněné renderování
{isLoading && <Spinner />}
{error && <ErrorMessage error={error} />}
{!isLoading && !error && <ProductList items={data} />}
// Kvíz: Proč je index jako key v listu problematický?
Cvičení — Todo aplikace
Napište kompletní Todo aplikaci: přidávání úkolů (input + Enter), označení jako hotovo (kliknutí), smazání, počítadlo zbývajících, filtr (vše/aktivní/hotové).
useState([]) pro seznam, každý todo = { id, text, done }, unikátní ID (Date.now()), .map() pro render, .filter() pro filtraci, immutabilní update (spread).React Foundations
Zvládáte JSX, komponenty, props, useState a renderování listů.
// Taháček
Komponenta skeleton
// Funkční komponenta s TypeScript
interface Props {
title: string;
count?: number;
onAction: () => void;
}
export function MojeKomponenta({ title, count = 0, onAction }: Props) {
const [state, setState] = React.useState(false);
return (
<div>
<h2>{title}</h2>
<p>{count}</p>
<button onClick={onAction}>Akce</button>
</div>
);
}
useState patterns
// Primitivní hodnota
const [val, setVal] = useState(0);
// Objekt — vždy spread!
const [obj, setObj] = useState({ a: 1, b: 2 });
setObj(prev => ({ ...prev, b: 3 }));
// Pole — immutabilní operace
const [arr, setArr] = useState([]);
setArr(prev => [...prev, novyItem]); // přidat
setArr(prev => prev.filter(x => x.id !== id)); // smazat
setArr(prev => prev.map(x => x.id === id ? {...x, done:true} : x)); // update