Menu

TypeScript interface vs type: Unterschiede und wann was

interface und type können beide Objektformen beschreiben, und meistens funktioniert beides. Die echten Unterschiede: Declaration Merging, Unions und Mapped Types, extends vs Intersections, implizite Index-Signaturen, Fehlermeldungen und Compiler-Performance, dazu eine klare Regel für die Wahl.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Zum Beschreiben der Form eines Objekts erledigen interface und type dieselbe Aufgabe, und Werte des einen sind dem anderen zuweisbar, wenn die Formen übereinstimmen. Die Unterschiede liegen an den Rändern: type kann Dinge benennen, die ein Interface nicht kann (Unions, Tupel, berechnete Typen), und ein Interface kann ein paar Dinge, die ein Typalias nicht kann (Merging, geprüftes extends).

TypeScript vergleicht Objekttypen nach Struktur, der Name der Deklaration spielt für die Zuweisbarkeit also keine Rolle. Es folgt die Liste der Stellen, an denen die Wahl doch eine Rolle spielt.

Vergleichstabelle

Featureinterfacetype
Objektformenjaja
Optional, readonly, Methoden, Index-Signaturenjaja
Genericsjaja
Unions (A | B)neinja
Tupel, primitive Typen, Funktionstypen für sichnein (Funktionstypen nur als Call Signatures)ja
Mapped und Conditional Typesneinja
Erweiternextends, Konflikte sind Fehler&, Konflikte werden zu never
Declaration Mergingjanein (doppelter Bezeichner)
Zuweisbar an Record<string, T>neinja, wenn die Eigenschaften passen
implements in einer Klassejaja, wenn es ein Objekttyp ist
Rekursive Definitionenjaja

Was nur type kann

Alles, was keine einzelne Objektform ist, braucht einen Typalias:

Nichts davon lässt sich mit interface schreiben (die letzten beiden werden unter Mapped Types und Conditional Types behandelt). Das ist der praktische Grund, warum jede Codebasis irgendwo type verwendet, egal was sie für Objektformen nutzt.

Was nur interface kann: Declaration Merging

Zwei interface-Deklarationen mit demselben Namen im selben Scope verschmelzen zu einer. Zwei type-Deklarationen mit demselben Namen sind der Fehler TS2300, Duplicate identifier.

interface Settings {
  theme: string;
}
interface Settings {
  fontSize: number;
}
const s: Settings = { theme: "dark", fontSize: 14 }; // needs both

type Options = { a: number };
type Options = { b: number }; // error TS2300: Duplicate identifier 'Options'.

Über Merging werden Bibliothekstypen von außen erweitert: eine Eigenschaft zum globalen Window hinzufügen, zum Request von Express oder zum Theme-Typ einer Bibliothek. Veröffentlichst du Typen, die Nutzer eventuell ergänzen müssen, nimm Interfaces. In deinem eigenen Anwendungscode passiert ein versehentliches Merging (zwei Skriptdateien deklarieren denselben globalen Interface-Namen) stillschweigend, außer beide deklarieren dieselbe Eigenschaft mit unterschiedlichen Typen, und das ist ein Argument, das manche Teams für type anführen.

extends vs Intersection

Ein Interface erweitert mit extends, ein Typalias mit &. Meist ergibt das dasselbe, aber eine widersprüchliche Eigenschaft wird unterschiedlich behandelt. extends meldet den Konflikt an der Deklaration:

index.ts(7,11): error TS2430: Interface 'Broken' incorrectly extends interface 'Base'.
  Types of property 'id' are incompatible.
    Type 'number' is not assignable to type 'string'.

Eine Intersection akzeptiert denselben Konflikt stillschweigend und macht die Eigenschaft zu never (ein string, der zugleich eine number ist). Der Fehler taucht erst später auf, wenn du versuchst, einen Wert zu erzeugen:

Der Fehler zeigt auf das Objekt, weit weg von der Deklaration, die ihn verursacht hat. Zum Aufbauen von Objekttypen aus anderen Objekttypen liefert extends die bessere Meldung.

Index-Signaturen: ein leicht übersehener Unterschied

Ein Typalias für einen Objekttyp bekommt eine implizite Index-Signatur, er lässt sich also dort übergeben, wo ein Record<string, unknown> erwartet wird. Ein Interface nicht. Das ist Absicht: Die Erklärung des TypeScript-Teams (Issue #15300 im TypeScript-Repository) lautet, dass ein Interface durch spätere Deklarationen ergänzt werden kann, eine abgeleitete Index-Signatur dafür also weniger sicher ist, und eine Änderung der Regel heute zu viel Code kaputt machen würde.

Der mit @ts-expect-error markierte Aufruf läuft trotzdem und gibt Alans Felder aus: Die Regel gilt beim Kompilieren. Das ist der übliche Grund für einen verwirrenden Fehler, wenn ein Interface-Wert an eine Hilfsfunktion für Logging, Serialisierung oder Abfragen übergeben wird, die mit Record<string, ...> typisiert ist. Stelle diese eine Deklaration auf type um, spreade den Wert oder typisiere den Parameter der Hilfsfunktion stattdessen mit einem Interface oder einem Generic.

Performance und Fehlermeldungen

Die Performance-Seite des TypeScript-Wikis (Abschnitt „Preferring Interfaces Over Intersections“) empfiehlt beim Zusammensetzen von Objekttypen interface Foo extends Bar, Baz { ... } statt type Foo = Bar & Baz & { ... }. Die Gründe: Ein Interface ist ein einziger flacher Objekttyp, der Konflikte zwischen Eigenschaften erkennt, Typbeziehungen zwischen Interfaces werden zwischengespeichert (Intersections als Ganzes nicht), und die Prüfung eines Werts gegen eine Intersection prüft jeden Bestandteil vor dem abgeflachten Typ. Der Unterschied zählt in großen Codebasen mit vielen zusammengesetzten Typen; bei ein paar gewöhnlichen Objektformen wirst du ihn nicht messen.

Dieselbe Seite merkt an, dass Interfaces besser angezeigt werden. Ein Interface erscheint in Hovern und Fehlermeldungen mit seinem Namen, während ein Intersection-Alias oft in seine Teile aufgelöst ausgegeben wird, was lange Meldungen schwerer lesbar macht.

Was verwenden

Eine Regel, die funktioniert:

  1. Objektformen: interface. Es liefert geprüftes extends, klarere Fehler bei großen Zusammensetzungen und lässt Nutzer von Bibliotheken es ergänzen. Das entspricht der Faustregel des TypeScript-Handbuchs: „interface verwenden, bis du Features von type brauchst“.
  2. Alles andere: type. Unions, Tupel, Funktionstypen, Literaltypen und alles, was mit Mapped, Conditional oder Template Literal Types gebaut wird.
  3. Ausnahme: Nimm type für eine Objektform, die zu Parametern mit Record<string, ...> passen muss, oder wenn du Merging bewusst verhindern willst.

Für alles type zu verwenden ist ebenfalls eine schlüssige Wahl, und viele Codebasen tun das. Beides wahllos zu mischen ist die eine Option, die du meiden solltest: Leser fragen sich dann, ob ein Unterschied beabsichtigt war.

Häufig gestellte Fragen

Was ist der Unterschied zwischen type und interface in TypeScript?

Beide beschreiben Objektformen und sind dafür austauschbar. type kann außerdem Unions, Tupel, primitive Typen sowie Mapped und Conditional Types benennen, was interface nicht kann. interface unterstützt Declaration Merging und extends, das auf widersprüchliche Eigenschaften prüft. Mit type geschriebene Objekttypen sind außerdem Typen mit Index-Signatur wie Record<string, unknown> zuweisbar, Interfaces nicht.

Sollte ich type oder interface verwenden?

Die Faustregel des TypeScript-Handbuchs lautet: interface verwenden, bis du ein Feature brauchst, das nur type hat. In der Praxis heißt das Interfaces für Objektformen und type für Unions, Tupel, Funktionstypen und berechnete Typen. Teams, die für alles type verwenden, fahren ebenfalls gut; wichtig ist eine einheitliche Regel.

Ist interface in TypeScript schneller als type?

Beim Zusammensetzen von Objekttypen in manchen Fällen ja. Die Performance-Hinweise des TypeScript-Teams empfehlen interface ... extends statt großer Intersections (A & B & { ... }), weil Beziehungen zwischen Interfaces zwischengespeichert werden und ein Interface ein einziger flacher Typ ist. Bei einer einfachen Objektform gibt es keinen nennenswerten Unterschied.

Kann eine Klasse einen Typalias implementieren?

Ja, wenn der Alias ein Objekttyp (oder eine Intersection von Objekttypen) ist: class Point implements PointType { ... } funktioniert. Eine Klasse kann keinen Union-Typ implementieren; das ist der Fehler TS2422.

Kann ein Interface einen Typalias erweitern?

Ja, solange der Alias ein Objekttyp ist: type Base = { id: string }; interface User extends Base { name: string } ist gültig. Einen Union-Alias kann es nicht erweitern. In die andere Richtung kann ein Typalias mit & auf einem Interface aufbauen.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S