Startseite / Artikel / Häufige TypeScript-Fehler in React und wie man sie behebt

Häufige TypeScript-Fehler in React und wie man sie behebt

Eine praktische Anleitung zu fünf häufig auftretenden TypeScript-Fehlerkategorien in React-Apps – Props, Ereignisse, State, asynchrone Daten und Kinderkomponenten – mit klaren Lösungen für jeden Fehler.

3774 Wörter

Falls Sie aus der Welt von JavaScript kommen, kann TypeScript anfangs beängstigend wirken. In den ersten Tagen scheint es, als würde der Compiler aktiv gegen Sie arbeiten: überall rote und gelbe wellenförmige Linien, rätselhafte Meldungen, die scheinbar zu nichts führen ... und irgendwann fragen Sie sich vielleicht, ob der Wechsel zu TypeScript wirklich eine gute Idee war, und überlegen, ob es nicht einfacher wäre, wieder zu reinem JavaScript zurückzukehren.

Doch hier ist der Punkt: TypeScript in eine React-Codebasis einzuführen, ist einer der klügsten Schritte, den Sie unternehmen können, wenn Ihnen die Aufrechterhaltung von hochwertigem Code im Laufe der Zeit wichtig ist.

Und diese Fehler und Warnungen, auf die Sie ständig stoßen? Es lohnt sich auf jeden Fall, durchzuhalten – Sie müssen nur lernen, die wiederkehrenden Muster dahinter zu erkennen.

Die meisten TypeScript-Probleme, auf die man beim Erstellen von React-Apps stößt, sind keineswegs zufällig. Dasselbe begrenzte aantal Fehler taucht immer wieder in verschiedenen Projekten auf, und nachdem man sie ein paar Mal behoben hat, erkennt man sie sofort und weiß genau, was sie bedeuten. In diesem Artikel werden fünf Kategorien von Fehlern vorgestellt, die in nahezu jedem mit TypeScript betriebenen React-Projekt auftreten, wobei erklärt wird, warum jeder Fehler auftritt und wie er gelöst werden kann.

Ein Rat, bevor man loslegt: Lesen Sie immer die gesamte Fehlermeldung. TypeScript-Fehler können auf den ersten Blick beängstigend wirken, folgen aber tatsächlich einer recht vorhersehbaren Struktur. Lassen Sie sich nicht von dem Textstrom verwirren – falls Sie unsicher sind, wo Sie anfangen sollen, ist in der Regel die letzte Zeile der Meldung der nützlichste Teil, auf den Sie sich konzentrieren sollten.

Nun kommen wir also dazu.

1. Fehler bei Component-Props

Props bilden den Kern jedes React-Komponenten, daher ist es verständlich, dass typbezogene Fehler mit Props in der Regel die ersten sind, auf die Entwickler stoßen.

Der Fehler: Die Eigenschaft ‘X’ existiert nicht im Typ '{}'.

Dies tritt auf, wenn Sie eine Prop in Ihrer Komponente verwenden, ohne ihr jemals eine Typdefinition zu geben.

// This won't give any errors in JavaScript...
function UserCard({ name, email }) {
  return (
    <div>
      <h3>{name}</h3>
      <p>{email}</p>
    </div>
  );
};

// But in TypeScript...
// Error: Parameter 'name' implicitly has an 'any' type
// Error: Parameter 'email' implicitly has an 'any' type

Dieser Fehler tritt einfach deshalb auf, weil den Props nie explizite Typen zugewiesen wurden.

Die Lösung: Deklarieren Sie eine Schnittstelle, die Ihre Props beschreibt.

interface UserCardProps {
  name: string;
  email: string;
};

function UserCard({ name, email}: UserCardProps) {
  return (
    <div>
      <h3>{name}</h3>
      <p>{email}</p>
    </div>
  );
};

Der Fehler: Der Typ ‘string’ kann nicht dem Typ ‘number’ zugewiesen werden.

Dieser Fehler ist ziemlich einfach zu verstehen: Es bedeutet, dass Sie eine Prop mit einem Wert des falschen Typs übergeben haben.

Die Lösung: Überprüfen Sie den Typ des Wertes, den Sie der Prop zuweisen.

interface ItemForSaleProps {
  imgUrl: string;
  itemName: string;
  amount: number;
  currency: string;
};

function ItemForSale({
  itemName,
  imgUrl,
  amount,
  currency
}: ItemForSaleProps) {
  return (
    <div class="item-for-sale">
      <img src={imgUrl} alt="item image" />
      <h5>{itemName}</h5>
      <p>{`${currency} ${amount.toFixed(2)}`}</p>
    </div>
  );
};

// Error happens when passing a string where a number is expected.
<ItemForSale
  imgUrl="api.example.com/image"
  itemName="Sample"
  amount="10.99"
  currency="USD"
/>

// Should be like this
<ItemForSale
  imgUrl="api.example.com/image"
  itemName="Sample"
  amount={10.99}
  currency="USD"
/>

Beachten Sie den Unterschied zwischen den beiden Versionen? Das ItemForSale-Komponente erwartet, dass amount ein Number-Wert ist; daher führt das Übergeben einer Zeichenkette zu einem Fehler, während das Übergeben eines tatsächlichen numerischen Wertes korrekt ist. Das ist genau das, wofür TypeScript da ist: potenzielle Fehler bereits vor dem Ausführen des Codes aufzudecken. In diesem Beispiel würde das Aufrufen von .toFixed(2) auf der Zeichenkette '10.99' tatsächlich zum Absturz beim Laufzeitausführung führen, doch TypeScript weist das Problem bereits während der Kompilierung aus.

Der Fehler: Die Eigenschaft 'X' fehlt im Typ '{}' und ist im Typ 'Props' erforderlich.

Dies tritt auf, wenn eine Eigenschaft in Ihrer Schnittstelle als erforderlich markiert ist, Sie aber beim Verwenden der Komponente vergessen, sie tatsächlich zu übergeben.

interface CustomButtonProps {
  label: string;
  onClick: () => void;
};

function CustomButton({ label, onClick }: CustomButtonProps) {
  return <button onClick={onClick}>{label}</button>;
};

// Bad usage:
<CustomButton label="Submit" />
// Error: Property 'onClick' is missing in type '{ label: string; }'
// but required in type 'ButtonProps'

Die Lösung: Überprüfen Sie zweimal sowohl die Definitionen der Eigenschaften in Ihrer Komponente als auch die Eigenschaften, die Sie tatsächlich beim Rendern übergeben.

// Correct usage:
<CustomButton label="Submit" onClick={handleSubmit} />

Manchmal gibt es Eigenschaften, die nicht immer benötigt werden. In solchen Fällen können Sie eine Eigenschaft mit einem ? als optional kennzeichnen. Denken Sie jedoch daran, dass Sie bei einer optionalen Eigenschaft entweder einen Standardwert oder eine Art Null-Prüfung bereitstellen müssen, um sicher mit ihrem Fehlen umzugehen.

interface CustomButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean;   // this prop is now optional
  className?: string;   // this one too
};

function CustomButton({ label, onClick, disabled = false, className }: CustomButtonProps) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={className}
    >
      {label}
    </button>
  );
};

// This component now works with or without the optional props
<CustomButton label="Submit" onClick={handleSubmit} />
<CustomButton label="Submit" onClick={handleSubmit} disabled={true} />

Nun, da optionale Eigenschaften eine Rolle spielen, gibt es noch einen weiteren Fehler, den man erwähnen sollte.

Der Fehler: Der Typ ‘X | undefined’ kann nicht dem Typ ‘X’ zugewiesen werden.

Durch die Markierung einer Eigenschaft als optional wird automatisch ‘undefined’ zu ihrem Typ hinzugefügt, was Probleme verursacht, wenn Sie diesen Wert ohne vorherige Prüfung auf seine Existenz verwenden.

interface ProfileProps {
  name: string;
  bio?: string;
};

function Profile({ name, bio }: ProfileProps) {
  return (
    <p>{name}</p>
    <p>{bio.toUpperCase()}</p>
    // Error: Object is possibly 'undefined'
  );
};

Im Allgemeinen gibt es drei Möglichkeiten, damit umzugehen:

Option 1: Geben Sie einen Standardwert bei der Dekonstruktion der Props an.

// bio is always a string, will render a default text when there's
// no bio provided
function Profile({ name, bio = 'No bio available' }: ProfileProps) {
  return (
    <p>{name}</p>
    <p>{bio.toUpperCase()}</p>
  );
};

Option 2: Verwenden Sie die optionale Kettensuche.

// returns undefined if bio is undefined
function Profile({ name, bio }: ProfileProps) {
  return (
    <p>{name}</p>
    <p>{bio?.toUpperCase()}</p>
  );
};

Option 3: Nutzen Sie bedingte Darstellung.

// will only render if bio exists
function Profile({ name, bio }: ProfileProps) {
  return (
    <p>{name}</p>
    <p>{bio && bio.toUpperCase()}</p>
  );
};

2. Fehler bei Event-Handlern

Jede React-Komponente, die auf Benutzerinteraktionen reagiert, benötigt Event-Handler, und TypeScript bietet sehr präzise Typen für jede Art von DOM-Event. Falsche Verwendung dieser Typen ist eine der häufigsten Quellen für Verwirrung bei Entwicklern, die mit typisierten React-Code arbeiten.

Das Problem: Der Parameter ‘e’ hat implizit den Typ ‘any’.

Dies tritt auf, wenn Sie einen Event-Handler als eigenständige Funktion außerhalb Ihres JSX schreiben und vergessen, den Event-Parameter zu annotieren.

// Error: Parameter 'e' implicitly has an 'any' type
const handleClick = (e) => {
  e.preventDefault();
};

Die Lösung: Fügen Sie dem Parameter den entsprechenden React-Event-Typ hinzu.

const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  e.preventDefault();
};

Beachten Sie: Wenn Sie den Handler direkt innerhalb Ihres JSX einfügen, kann TypeScript den Typ selbst erkennen, sodass in diesem Fall dieser spezielle Fehler nicht auftreten wird.

<button onClick={(e) => {
  e.preventDefault() // e is automatically React.MouseEvent<HTMLButtonElement>
}}>
  Click here
</button>

Das Problem: Die Eigenschaft ‘value’ existiert nicht beim Typ ‘EventTarget’.

Dies ist vermutlich der am häufigsten gesuchte TypeScript-Fehler unter React-Entwicklern – er ist zudem einer der schwierigsten, wenn man ihn zum ersten Mal erlebt. Er tritt auf, wenn man versucht, e.target.value in einem Change-Handler zu lesen.

// This will give an error because TS doesn't know target is an input element.
const handleChange = (e: React.ChangeEvent) => {
  console.log(e.target.value);
  // Error: Property 'value' does not exist on type 'EventTarget'
};

Der ursächliche Grund ist, dass EventTarget eine generische DOM-Schnittstelle ist, weshalb TypeScript nicht erkennen kann, dass es sich in diesem spezifischen Handler tatsächlich um ein <input>-Element handelt, das zufällig eine value-Eigenschaft bereitstellt.

Lösung: Geben Sie den spezifischen Elementtyp als generischen Parameter zum Ereignistyp an.

// TypeScript now knows the target is an HTMLInputElement
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  console.log(e.target.value);
};

Das gleiche Muster gilt für andere Elemente – bei einem select-Element sollte beispielsweise HTMLInputElement durch HTMLSelectElement ersetzt werden. Hier ist eine kurze Übersicht zu einigen der am häufigsten verwendeten Ereignistypen:

// Click events
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void
onClick: (e: React.MouseEvent<HTMLDivElement>) => void

// Input change events
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void

// Form submission
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void

// Keyboard events
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void
onKeyUp: (e: React.KeyboardEvent<HTMLInputElement>) => void

// Focus events
onFocus: (e: React.FocusEvent<HTMLInputElement>) => void
onBlur: (e: React.FocusEvent<HTMLInputElement>) => void

Da wir gerade beim Thema Event-Handler sind, fragen Sie sich vielleicht, wie man diese richtig typisiert, wenn man sie als Props an Kindkomponenten weitergibt. In diesem Fall sollten sie als Funktionen typisiert werden, die das entsprechende Event-Objekt entgegennehmen.

interface SearchInputProps {
  onSearch: (e: React.ChangeEvent<HTMLInputElement>) => void;
  onSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
};

function SearchInput({ onSearch, onSubmit }: SearchInputProps) {
  return (
    <form onSubmit={onSubmit}>
      <input type="text" onChange={onSearch} />
      <button type="submit">Search</button>
    </form>
  );
};

Und wenn ein bestimmter Handler das Event überhaupt nicht erhalten muss, kann man seinen Typ entsprechend vereinfachen.

interface SearchInputProps {
  onClick: () => void;
  onHover: () => void;
};

3. useState und useRef

Hooks stehen im Zentrum der modernen React-Entwicklung, daher ist es nicht überraschend, dass useState und useRef ihre eigenen Besonderheiten in TypeScript aufweisen.

Das Problem: Ein Argument vom Typ ‘X’ kann nicht dem Parameter vom Typ ‘never’ zugewiesen werden.

Das ist einer der verwirrendsten Fehler, auf die man stoßen kann. Er tritt auf, wenn man useState mit einem leeren Array initialisiert, wodurch TypeScript den Typ des States als never[] ableitet. So sieht das aus:

// TypeScript infers state type as never[]
const [items, setItems] = useState([]);

// So later when we try to set a value...
setItems([{ id: 1, name: 'Item 1' }]);
// Error: Argument of type '{ id: number; name: string; }[]' is not assignable
// to parameter of type 'never[]'

Der Grund dafür: Wenn der Anfangswert des States ein leeres Array ist, hat TypeScript keine Möglichkeit zu erraten, welche Elemente später darin enthalten sein werden, weshalb es standardmäßig den Typ never[] verwendet.

Die Lösung: Geben Sie immer einen expliziten Typargument an, wenn Ihr Anfangszustand ein leeres Array oder null ist.

interface Item {
  id: number;
  name: string;
}

// ✅ Explicitly typed
const [items, setItems] = useState<Item[]>([]);
const [selectedItem, setSelectedItem] = useState<Item | null>(null);

In diesem Auszug ist der Zustand selectedItem explizit auf ein Objekt vom Typ Item oder null festgelegt. Immer dann, wenn Sie erwarten, dass ein Zustandswert zunächst leer ist und später ausgefüllt wird, müssen Sie dies für TypeScript klar angeben – andernfalls treten folgende Fehler auf:

Type 'null' ist nicht assignable zu type 'X'.

interface User {
  id: number;
  name: string;
  email: string;
}

// TypeScript infers state as User, not User | null
const [user, setUser] = useState<User>({} as User); // Dangerous cast

// Later...
if (user.name) { /* ... */ }
// This might not catch the case where user is empty

Die Lösung: Verwenden Sie einen Union-Typ, der null enthält, damit TypeScript versteht, dass die Daten noch nicht geladen wurden.

// Correctly typed, can be type User or null
const [user, setUser] = useState<User | null>(null);

// Now TypeScript forces you to handle the null case
if (user) {
  console.log(user.name); // TypeScript knows user is User here
}

// Or with optional chaining:
console.log(user?.name); // string | undefined

Dieselbe Logik gilt, wenn Ihr Zustand ein komplexeres Objekt enthält. Angenommen, Sie modellieren den Zustand eines Kontaktformulars mit mehreren Feldern – der richtige Ansatz ist es, zunächst eine Schnittstelle zu definieren, die diese Struktur beschreibt.

interface FormState {
  name: string;
  email: string;
  message: string;
  isSubmitting: boolean;
}

function SubmitMessageForm() {
  const [form, setForm] = useState<FormState>({
    name: '',
    email: '',
    message: '',
    isSubmitting: false,
  });

  // Then do partial updates with spread operator
  const handleChange = (field: keyof FormState, value: string) => {
    setForm(prevState => ({ ...prevState, [field]: value }));
  };
}

Das Problem: Das Objekt kann möglicherweise den Wert null haben.

Das tritt ständig auf, wenn ein mit useRef erstellter Referenzwert anfangs null ist und eigentlich auf ein DOM-Element verweisen soll. TypeScript weist jeden Versuch hin, diese Referenz zu verwenden, solange noch nicht bestätigt wurde, dass sie tatsächlich einen Wert enthält.

const inputRef = useRef<HTMLInputElement>(null);

// Typescript here knows that inputRef.current might be null
inputRef.current.focus();

// Error: Object is possibly 'null'

Die Lösung: Überprüfen Sie, ob current bereits gesetzt wurde, bevor Sie es verwenden. Sobald TypeScript diese Überprüfung sieht, hört es auf, Warnungen auszusprechen, da es nicht mehr nachweisen kann, dass der Wert null sein könnte.

// Null check before use
const handleFocus = () => {
  if (inputRef.current) {
    inputRef.current.focus();
  }
};

// Or if you're more into one-liners, you can use optional chaining
inputRef.current?.focus();

Vor dem Übergang zur nächsten Kategorie gibt es eine wichtige Änderung, die erwähnt werden sollte. React 19 hat das Verhalten von useRef so geändert, dass dies viele Entwickler verwirrt – insbesondere die Unterscheidung zwischen RefObject<T|null> und MutableRefObject<T>. Entscheidend für das Verhalten ist nun nicht der anfängliche Wert, den man überlässt, sondern der generische Typparameter, den man dem Hook gibt.

Vor React 19 ergab die Aufrufung von useRef(null) immer einen MutableRefObject<T>, sodass current stets neu zugewiesen werden konnte.

// This worked fine
const ref = useRef<HTMLInputElement | null>(null);
ref.current = someElement;

Ab React 19 bestimmt der von Ihnen angegebene generische Typ, ob current schreibbar ist.

const ref = useRef<HTMLInputElement>(null);
ref.current = someElement;
// The above will result in an error.

Hier behandelt React 19 den ref, da der generische Parameter ein HTML-Element-Typ ist, als einen, der auf das DOM verweist. Daher wird er schreibgeschützt und von React selbst verwaltet.

Falls Sie hingegen einen ref benötigen, um einen veränderbaren Wert statt eines DOM-Nodes zu speichern, tun Sie Folgendes:

// Let's suppose we're building a timer
const ref = useRef<ReturnType<typeof setTimeout> | null>(null);
ref.current = setTimeout(() => {}, 1000);

Das funktioniert, weil der dem Generischen übergebene Typ kein HTML-Element-Typ ist. Daher behandelt React ihn als einen gewöhnlichen, veränderbaren ref – current bleibt weiterhin zuweisbar.

Die Regel für React 19: Wenn der generische Typ ein HTML-Element-Typ ist, wird current schreibgeschützt und von React gesteuert. Bei jedem anderen Typ bleibt current schreibbar. Kurz gesagt:

  • useRef<HTMLElement>(null) für von React verwaltete DOM-Node (schreibgeschützt)
  • useRef<T|null>(null) für jeden anderen veränderbaren Wert, den Sie selbst speichern möchten
  • 4. Asynchrone Daten und API-Fehler

    Sobald Sie anfangen, Daten von einer API abzurufen, treten neue TypeScript-Fehler auf, da die heruntergeladenen Daten zunächst den Typ unknown haben und mehrere Umwandlungen durchlaufen müssen, bevor sie sicher verwendet werden können.

    Der Fehler: Der Typ ‘unknown’ kann nicht dem Typ ‘X’ zugewiesen werden.

    Durch Default wird das Ergebnis eines Fetch-Aufrufs als unknown typisiert, da TypeScript keine eingebauten Informationen darüber hat, welche Struktur ein bestimmter Endpunkt zurückgibt.

    // Fetch response is unknown
    const response = await fetch('/api/users');
    const data = await response.json(); // data: any (in older TS) or unknown
    
    // So when trying to use the fetched data:
    const userName = data.name;
    
    // With the code above, you might get a warning, something like
    // 'data' is of type 'unknown'
    

    Die Lösung: Deklarieren Sie einen expliziten Typ für die Antwort Ihrer API.

    interface User {
      id: number;
      name: string;
      email: string;
    };
    

    Daraufhin haben Sie im Allgemeinen zwei Möglichkeiten, diesen Typ anzuwenden:

    // Option 1: Type assertion. Only use this when you trust the API shape.
    const response = await fetch('/api/users/1');
    const data = await response.json() as User;
    console.log(data.name); // You'll see that you have a string here
    
    //////////////////////////////////////////////////////////////////////////
    
    // Option 2: Create a generic fetch wrapper, this is a safer approach
    // and it's reusable.
    async function fetchJSON<T>(url: string): Promise<T> {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`HTTP error: ${response.status}`);
      }
      return response.json() as Promise<T>;
    }
    const user = await fetchJSON<User>('/api/users/1');
    console.log(user.name); // TypeScript now knows this is a string
    

    5. Kinderelemente und JSX-Fehler

    Komponenten in React akzeptieren und rendern regelmäßig children, und TypeScript bietet mehrere überschneidende Typen an, um zu beschreiben, was Kinder sein können. Zu wissen, wie sich diese unterscheiden, hilft Ihnen, eine ganze Reihe von Typfehlern zu vermeiden.

    Drei Typen werden oft so verwendet, als wären sie austauschbar, obwohl das nicht der Fall ist:

    • ReactNode
    • ReactElement
    • JSX.Element

    Von diesen sind ReactNode und ReactElement tatsächlich unterschiedlich, während JSX.Element im Grunde nur ein anderer Name für ReactElement ist.

    import { ReactNode, ReactElement } from 'react';
    
    // ReactNode is the most permissive one, accepts basically
    // everything React can render:
    // string, number, boolean, null, undefined, ReactElement, arrays...
    
    type ReactNode =
      | ReactElement
      | string
      | number
      | boolean
      | null
      | undefined
      | ReactPortal
      | Iterable<ReactNode>;
    
    // ReactElement is literally a React element created by
    // JSX or React.createElement
    // It does not include string, number, null, undefined
    type ReactElement = {
      type: string | ComponentType;
      props: any;
      key: string | null;
    };
    

    Als Faustregel: Wählen Sie für die children-Eigenschaft standardmäßig ReactNode, es sei denn, Sie haben einen konkreten Grund, den Typ einzugrenzen.

    // Most flexible: use ReactNode for children in most cases
    interface CardProps {
      children: ReactNode;
    };
    
    // And use ReactElement when the requirements are not very flexible
    interface StrictWrapperProps {
      children: ReactElement;
    };
    

    Es lohnt sich, den Unterschied zwischen ReactNode und ReactElement genauer zu erläutern.

    Ein ReactNode kann wie eines von folgenden aussehen:

    const example1 = <div>Hello</div>;        // ReactElement ✅
    const example2 = "Hello";                 // string ✅
    const example3 = 42;                      // number ✅
    const example4 = true;                    // boolean ✅ (renders nothing)
    const example5 = null;                    // null ✅ (renders nothing)
    const example6 = undefined;               // undefined ✅ (renders nothing)
    const example7 = [<div />, <span />];     // array of ReactElements ✅
    
    // All of the above are ReactNode
    

    Ein ReactElement hingegen ist stärker eingeschränkt:

    const example1 = <div>Hello</div>;
    const example2 = <Button onClick={someHandlerFn}>Submit</Button>;
    const example3 = React.createElement('div', null, 'Hello');
    
    // Under the hood, every ReactElements look like this:
    {
      type: 'div',
      props: { children: 'Hello' },
      key: null,
    }
    

    Der Fehler: Die Typ ‘X’ kann nicht dem Typ ‘ReactNode’ zugewiesen werden.

    Dies tritt in der Regel auf, wenn man versucht, einen Wert anzuzeigen, den TypeScript nicht als gültigen Darstellungsinhalt erkennt.

    // Objects are not a valid React children
    interface User {
      name: string;
      email: string;
    }
    
    
    // Then if you try to do this
    function DisplayUser({ user }: { user: User }) {
      return <div>{user}</div>;
    }
    
    // It'll probably give you an error that looks something like...
    // Error: Type 'User' is not assignable to type 'ReactNode'
    // Objects are not valid React Children
    

    Die Lösung: Zeichnen Sie einzelne Felder anstelle des gesamten Objekts aus.

    function DisplayUser({ user }: { user: User }) {
      return (
        <div>
          <p>{user.name}</p>
          <p>{user.email}</p>
        </div>
      );
    }
    

    Der Fehler: Der JSX-Element-Typ ‘X’ weist keine Konstrukt- oder Aufrufsignaturen auf.

    Das kann anfangs verwirrend sein. Es tritt auf, wenn man einen Wert an eine Stelle übergeben möchte, von der er als Komponente behandelt werden soll, aber TypeScript hat keine Möglichkeit, zu überprüfen, ob es sich tatsächlich um eine Komponente handelt.

    // TypeScript doesn't know 'icon' is a valid component
    interface ButtonProps {
      icon: object; // too vague
    };
    
    // So when you try this...
    function Button({ icon: Icon }: ButtonProps) {
      return <Icon />;
    }
    
    // The above code will give you the error
    // Error: JSX element type 'Icon' does not have any construct
    // or call signatures
    

    Die Lösung: Typisieren Sie dynamische Komponenten mit React.ComponentType:

    import { ComponentType } from 'react';
    
    interface ButtonProps {
      icon: ComponentType;
    };
    
    function Button({ icon: Icon }: ButtonProps) {
      return (
        <button>
          <Icon />
        </button>
      );
    }
    
    // TypeScript now knows Icon is a valid component
    

    Falls die Komponente auch Props entgegennimmt, können Sie diese explizit beschreiben:

    interface IconProps {
      size?: number;
      color?: string;
    };
    
    interface ButtonProps {
      icon: ComponentType<IconProps>;
    };
    
    function Button({ icon: Icon }: ButtonProps) {
      return (
        <button>
          <Icon size={12} color="white" />
        </button>
      );
    }
    
    // So now the props are typed too
    

    Ein nützlicher Shortcut, den man kennen sollte, ist der eingebaute Utility-Typ PropsWithChildren von React. Er fügt automatisch children?: ReactNode zu jeder Props-Schnittstelle hinzu, um Ihnen das erneute Deklarieren dieses Feldes zu ersparen:

    import { PropsWithChildren } from 'react';
    
    // Now instead of doing this
    interface CardProps {
      title: string;
      children?: ReactNode;
    };
    
    // You can do this
    type CardProps = PropsWithChildren<{ title: string }>;
    
    // So with this, instead of explicitly typing children manually,
    // you can use the above code
    
    function Card({ title, children }: CardProps) {
      return (
        <div>
          <h1>{title}</h1>
          <div>{children}</div>
        </div>
      );
    }
    

    Das Problem: Darstellung von Elementlisten

    TypeScript legt strenge Regeln für Schlüssel in dargestellten Listen fest, doch die meisten Typfehler, auf die man hier stößt, resultieren tatsächlich aus der Typisierung der Elemente selbst und nicht von den Schlüsseln.

    // Items might be undefined, or item.id might not be a valid key type
    function ItemList({ items }: { items: Item[] | undefined }) {
      return (
        <ul>
          {
            items.map(item => (
              <li key={item.id}>{item.name}</li>
            ))
          }
        </ul>
      );
    }
    
    // Error: Object is possibly 'undefined'
    

    Die Lösung: Schützen Sie sich vor undefined-Werten und stellen Sie sicher, dass die Typisierung korrekt ist:

    function ItemList({ items = [] }: { items?: Item[] }) {
      return (
        <ul>
          {
            items.map((item) => (
              <li key={item.id}>{item.name}</li>
            ))
          }
        </ul>
      );
    }
    

    Zum Abschluss…

    TypeScript-Fehler wirken nicht mehr wie Hindernisse, sobald man sie als Hinweise betrachtet. Dieser Wandel tritt ein, in dem Moment, in dem man nicht mehr mit „Uff, schon wieder“ reagiert, sondern stattdessen fragt: ‚Was versucht TypeScript mir hier eigentlich mitzuteilen?‘

    Die in diesem Leitfaden behandelten Fehler sind solche, auf die Sie bei der Arbeit mit React und TypeScript ständig stoßen werden. Was einen Entwickler, der ständig mit dem Typensystem zu kämpfen hat, von einem unterscheidet, der damit problemlos arbeiten kann, liegt in der Regel nur am Mustererkennen – nichts weiter. Zu diesem Zeitpunkt kennen Sie bereits die relevanten Muster.