Komponenten und Props in React mit TypeScript typisieren
Komponenten und Props typisieren
~15 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026
Jetzt migrieren wir ProductCard – die am häufigsten wiederverwendete Komponente im Projekt, und ein perfektes Beispiel dafür, wie TypeScript genau die Fehler verhindert, die bei Props-Weitergabe über mehrere Dateien hinweg entstehen.
ProductCard.jsx zu ProductCard.tsx: das Props-Interface
import { memo, useState } from 'react';
interface ProductCardProps {
name: string;
price: number;
imageUrl: string;
onSelect: () => void;
onAddToCart: () => void;
}
function ProductCard({ name, price, imageUrl, onSelect, onAddToCart }: ProductCardProps) {
const [isFavorite, setIsFavorite] = useState(false);
function handleFavoriteClick(event: React.MouseEvent) {
event.stopPropagation();
setIsFavorite(!isFavorite);
}
function handleAddToCartClick(event: React.MouseEvent) {
event.stopPropagation();
onAddToCart();
}
return (
<div className="product-card" onClick={onSelect}>
<img src={imageUrl} alt={name} className="product-card__image" />
<div className="product-card__info">
<h3 className="product-card__name">{name}</h3>
<p className="product-card__price">${price.toFixed(2)}</p>
</div>
<button className="product-card__favorite" onClick={handleFavoriteClick}>
{isFavorite ? '♥' : '♡'}
</button>
<button className="product-card__add-to-cart" onClick={handleAddToCartClick}>
In den Warenkorb
</button>
</div>
);
}
export default memo(ProductCard);Die Details, die wichtig sind
interface ProductCardPropsbeschreibt die VOLLSTÄNDIGE öffentliche Schnittstelle der Komponente – jeder, der die Datei liest, sieht SOFORT (ohne den kompletten Funktionskörper zu lesen), welche Props Pflicht sind und welchen Typ sie haben.onSelect: () => void– die Pfeil-Syntax innerhalb eines Interfaces beschreibt eine FUNKTIONSSIGNATUR: "eine Funktion, die keine Argumente nimmt und nichts zurückgibt" (void, nichtundefined– der Unterschied ist Thema des TypeScript-Sprach-Tutorials).event: React.MouseEvent– der Typ für einen synthetischen React-Maus-Event; ohne diese Annotation würde TypeScripteventimplizit alsanybehandeln, was JEDE Typprüfung darauf abschaltet.memo(ProductCard)ganz am Ende funktioniert UNVERÄNDERT – TypeScript's Typinferenz erkennt automatisch, dass das Ergebnis weiterhin eine Komponente mit denselben Props ist.
Den Vorteil live erleben: ein absichtlicher Tippfehler
Öffnen Sie ProductListPage.jsx (noch nicht migriert) und ändern Sie testweise onAddToCart={{...}} zu onAddToCard={{...}} (vertauschtes "r"/"d"). Da ProductListPage.jsx selbst noch JavaScript ist, meldet der Editor HIER noch nichts – ändern Sie stattdessen versuchsweise EINEN Aufruf von <ProductCard ... /> testweise in einer .tsx-Datei mit dem Tippfehler: Ihr Editor unterstreicht onAddToCard ROT und zeigt "Property 'onAddToCard' does not exist on type 'ProductCardProps'" – VOR dem Ausführen, nicht erst beim Klick-Test im Browser.
Bonus: children typisieren (für spätere Komponenten)
Für Komponenten mit children (wie unser Modal aus Kapitel 38) gibt es einen eingebauten React-Typ:
import { ReactNode } from 'react';
interface ModalProps {
isOpen: boolean;
onClose: () => void;
children: ReactNode;
}ReactNode ist der breiteste, "alles was React rendern kann"-Typ (Strings, Zahlen, JSX-Elemente, Arrays davon, null, ...) – der richtige Typ für children, wenn Sie NICHT einschränken wollen, was übergeben werden darf.
Optionale Props mit dem Fragezeichen
Erinnern Sie sich an size = 48 als Default-Wert-Muster aus der "React Native Referenz"-Serie (Avatar-Thema)? Das TypeScript-Äquivalent für "diese Prop ist optional" ist ein ? im Interface:
interface AvatarProps {
name: string;
imageUrl?: string; // optional - kann weggelassen werden
size?: number; // optional, mit Default-Wert im Funktionskopf
}
function Avatar({ name, imageUrl, size = 48 }: AvatarProps) {
// ...
}Tipp: Faustregel für die Migrationsreihenfolge in einem echten Projekt: von "innen nach außen" bzw. von "Blättern zu Wurzeln" – kleine, wiederverwendbare Komponenten OHNE viele Abhängigkeiten (wie ProductCard) zuerst, komplexe Seiten-Komponenten mit vielen Imports (ProductListPage) zuletzt. So bleibt der Rest der App während der schrittweisen Migration lauffähig.