Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

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

src/components/ProductCard.tsx
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 ProductCardProps beschreibt 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, nicht undefined – 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 TypeScript event implizit als any behandeln, 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.