Následující text obsahuje: popis struktury projektu Ionic + React; ukázku vytvoření první komponenty; zadání.
Po každém kroku aplikaci uložte a vyzkoušejte.
Nový projekt vytvoříte přes rozšíření WebNative ve VS Code. V postranním panelu klikněte na ikonu WebNative a zvolte Start a new project. Jako framework vyberte React+Ionic, jako template Blank. Rozšíření automaticky vygeneruje celou strukturu projektu včetně závislostí.
Aplikaci spustíte tlačítkem Run v panelu WebNative.
Zajímají nás dvě složky:
pages/ (celé obrazovky) a components/ (části UI, které se mohou opakovat).
Stránka je jako plátno – obsahuje záhlaví a scrollovatelný obsah.
Komponenta je znovupoužitelný blok UI – jednou napsat, použít kolikrát chceme.
Vytvořte soubor src/components/MyCard.tsx.
Komponenta zatím zobrazí jen kartu se dvěma tlačítky.
// src/components/MyCard.tsx
import { IonCard, IonCardContent, IonCardHeader,
IonCardTitle, IonButton } from '@ionic/react';
// Komponenta je funkce, která vrací JSX (HTML-like kód)
function MyCard() {
return (
<IonCard>
<IonCardHeader>
<IonCardTitle>My Card</IonCardTitle>
</IonCardHeader>
<IonCardContent>
<IonButton expand="block">Calculate</IonButton>
<IonButton expand="block">Reset</IonButton>
</IonCardContent>
</IonCard>
);
};
// Bez exportu by soubor nešlo importovat odjinud
export default MyCard;
Kód uvnitř return () vypadá jako HTML, ale je to JSX
– speciální syntaxe Reactu pro popis UI. Přípona souboru .tsx přesně říká
co soubor obsahuje: TypeScript +
X (JSX).
Každá Ionic komponenta (IonCard, IonButton apod.)
musí být naimportována na začátku souboru.
Otevřete src/pages/Home.tsx. Odstraňte výchozí <ExploreContainer />
a přidejte svoji komponentu.
// src/pages/Home.tsx
import { IonContent, IonHeader, IonPage,
IonTitle, IonToolbar } from '@ionic/react';
// Import naší komponenty – cesta je relativní od tohoto souboru
import MyCard from '../components/MyCard';
function Home() {
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>My App</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent className="ion-padding">
{/* Vložíme komponentu jako self-closing tag */}
<MyCard />
</IonContent>
</IonPage>
);
};
export default Home;
Po uložení uvidíte kartu se dvěma tlačítky. Zkuste <MyCard />
napsat dvakrát – zobrazí se dvě karty ze stejného kódu. To je smysl komponent.
Aby byly komponenty opravdu znovupoužitelné, musíme do nich umět poslat data zvenku. Tomu říkáme props. Typ props popíšeme pomocí interface (TypeScript popis toho, co komponenta očekává).
// src/components/MyCard.tsx
import { IonCard, IonCardContent, IonCardHeader,
IonCardTitle, IonButton } from '@ionic/react';
// Interface popisuje, jaké props komponenta přijímá
interface MyCardProps {
calcText: string;
resetText: string;
}
// Props přijmeme jako parametr a rozbalíme je ({ ... })
function MyCard({ calcText, resetText }: MyCardProps) {
return (
<IonCard>
<IonCardHeader>
<IonCardTitle>My Card</IonCardTitle>
</IonCardHeader>
<IonCardContent>
{/* Složené závorky = výraz v JSX – vypíšeme hodnotu proměnné */}
<IonButton expand="block">{calcText}</IonButton>
<IonButton expand="block">{resetText}</IonButton>
</IonCardContent>
</IonCard>
);
};
export default MyCard;
V Home.tsx předáme text jako atributy:
<MyCard calcText="Calculate BMI" resetText="Reset" />
Zkuste zadat jiné texty nebo přidat třetí prop. Každé vložení komponenty může mít jiné hodnoty.
Přidáme výsledek a zkusíme ho měnit pomocí obyčejné proměnné.
Spoiler: nebude to fungovat – a to je důvod, proč existuje useState.
// src/components/MyCard.tsx – ŠPATNÁ varianta (záměrně)
function MyCard({ calcText, resetText }: MyCardProps) {
let result = 0; // Obyčejná proměnná
// Lze zapsat i jako arrow function: const handleCalc = () => {
function handleCalc() {
result = Math.round(Math.random() * 40 + 10);
console.log('New value:', result); // V konzoli se změní...
}
// Lze zapsat i jako arrow function: const handleReset = () => {
function handleReset() {
result = 0;
}
return (
<IonCard>
<IonCardContent>
<IonButton expand="block" onClick={handleCalc}>{calcText}</IonButton>
<IonButton expand="block" onClick={handleReset}>{resetText}</IonButton>
<p>Result: {result}</p> {/* ...ale tady se nic nezmění! */}
</IonCardContent>
</IonCard>
);
};
F12 → záložka Console) a klikejte na tlačítko.
V konzoli uvidíte nová čísla – ale na obrazovce zůstane stále „Result: 0".
React totiž neví, že se proměnná změnila, a stránku nepřekreslí.
K tomu potřebujeme useState.
Nahradíme obyčejnou proměnnou hookem useState.
Ten je propojený s Reactem – při změně hodnoty se komponenta automaticky překreslí.
const [result, setResult] = useState(0);
// ↑ ↑ ↑
// čteme měníme výchozí hodnota
// src/components/MyCard.tsx – SPRÁVNÁ varianta
import { useState } from 'react'; // ← přidat import
import { IonCard, IonCardContent, IonButton } from '@ionic/react';
interface MyCardProps {
calcText: string;
resetText: string;
}
function MyCard({ calcText, resetText }: MyCardProps) {
// let result = 0; ← toto nefungovalo
const [result, setResult] = useState(0); // ← toto funguje
// Lze zapsat i jako arrow function: const handleCalc = () => {
function handleCalc() {
const newResult = Math.round(Math.random() * 40 + 10);
setResult(newResult); // Zavolání setResult spustí překreslení
}
// Lze zapsat i jako arrow function: const handleReset = () => {
function handleReset() {
setResult(0);
}
return (
<IonCard>
<IonCardContent>
<IonButton expand="block" onClick={handleCalc}>{calcText}</IonButton>
<IonButton expand="block" onClick={handleReset}>{resetText}</IonButton>
<p>Result: {result}</p>
</IonCardContent>
</IonCard>
);
};
export default MyCard;
Co když chceme výsledek zobrazit v Home.tsx, ne uvnitř karty?
Potomek nemůže přímo měnit stav rodiče – místo toho mu rodič předá
funkci (callback), kterou potomek zavolá s výsledkem.
Stav přesuneme z MyCard do Home.
Komponenta se postará jen o výpočet a předání hodnoty nahoru.
// src/components/MyCard.tsx
import { IonCard, IonCardContent, IonButton } from '@ionic/react';
interface MyCardProps {
calcText: string;
resetText: string;
onResult: (value: number) => void; // Callback prop – funkce kam pošleme číslo
}
function MyCard({ calcText, resetText, onResult }: MyCardProps) {
// Lze zapsat i jako arrow function: const handleCalc = () => {
function handleCalc() {
const newResult = Math.round(Math.random() * 40 + 10);
onResult(newResult); // Pošleme výsledek rodiči
}
// Lze zapsat i jako arrow function: const handleReset = () => {
function handleReset() {
onResult(0);
}
return (
<IonCard>
<IonCardContent>
<IonButton expand="block" onClick={handleCalc}>{calcText}</IonButton>
<IonButton expand="block" onClick={handleReset}>{resetText}</IonButton>
</IonCardContent>
</IonCard>
);
};
export default MyCard;
// src/pages/Home.tsx
import { useState } from 'react';
import { IonContent, IonHeader, IonPage,
IonTitle, IonToolbar } from '@ionic/react';
import MyCard from '../components/MyCard';
function Home() {
// Stav žije tady – Home rozhoduje, co s výsledkem udělá
const [result, setResult] = useState(0);
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>My App</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent className="ion-padding">
{/* onResult dostane setResult – MyCard ho zavolá s vypočtenou hodnotou */}
<MyCard
calcText="Calculate"
resetText="Reset"
onResult={setResult}
/>
{/* Výsledek zobrazujeme zde v rodiči */}
<p>Result: {result}</p>
</IonContent>
</IonPage>
);
};
export default Home;
MyCard neví nic o tom, co se s výsledkem stane – jen zavolá funkci,
kterou dostala jako prop s názvem onResult. Jinými slovy:
funkce setResult z rodiče nyní v potomkovi vystupuje pod zapůjčeným jménem
onResult.
Rodič (Home) si tak rozhoduje, co s číslem udělá. Stav žije vždy v rodičovské komponentě,
dítě ho mění přes tuto propůjčenou funkci (tzv. callback prop). Tomuto vzoru se v Reactu říká lifting
state up.
export default.<MyCard /> po jejím importu.onResult) umožní potomkovi změnit stav v rodiči.Navazující úkoly:
MyCard přidejte
IonInput pro zadání váhy (kg) a výšky (cm). Místo náhodného čísla
vypočítejte skutečné BMI.
BmiResult – vytvořte novou komponentu,
která přijímá výsledek BMI a zobrazuje ho i s textovou kategorií.
BmiHistory – vytvořte komponentu
pro zobrazení historie výpočtů.
onChange a
hodnota je vyčítána z e.target.value.
V Ionicu je však u komponent pro vkládání definována událost onIonInput a samotná hodnota je vždy
zabalena do vnitřního objektu detail.
onIonInput={(e) => setMyPhoneNumber(e.detail.value || '')}
const [history, setHistory] = useState<number[]>([]); // 1. Vytvoření
setHistory([...history, novyVysledek]); // 2. Aktualizace
Více informací lze najít v oficiální dokumentaci: