Menu

TypeScript Intersection Types: Typen mit & kombinieren

Ein Intersection-Typ A & B beschreibt einen Wert, der zugleich A und B ist, also jeden Member beider hat. Wie du Objekttypen mit & kombinierst, warum widersprüchliche Eigenschaften zu never werden, wie Intersections von Unions nur die gemeinsamen Member behalten und wann stattdessen extends passt.

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

Ein Intersection-Typ, geschrieben A & B, beschreibt einen Wert, der zugleich ein A und ein B ist. Bei Objekttypen heißt das, dass der Wert jede Eigenschaft beider hat. So kombinierst du vorhandene Typen, ohne die Eigenschaften erneut auszuschreiben.

Beim zweiten Objekt fehlt team, also ist es der Compilerfehler TS2322, und die nächste Zeile der Meldung sagt Property 'team' is missing ... but required in type 'Employee'. Ein Staff-Wert lässt sich überall übergeben, wo ein Person oder ein Employee erwartet wird.

Objekttypen kombinieren

& funktioniert mit jeder Mischung aus Typaliassen, Interfaces und Inline-Objekttypen sowie mit generischen Typparametern. Im letzten Fall ist es schwer zu ersetzen: Eine Funktion, die jedem erhaltenen Objekt Eigenschaften hinzufügt, kann das genau ausdrücken.

Der Aufrufer behält den genauen Typ dessen, was er übergeben hat (title, words), plus die zwei hinzugefügten Eigenschaften. Ein interface ... extends kann „was auch immer T ist, plus diese“ nicht ausdrücken, weil ein Interface keinen Typparameter erweitern kann.

Widersprüchliche Eigenschaften werden zu never

Deklarieren beide Seiten dieselbe Eigenschaft, ist ihr Typ die Intersection der beiden. Haben diese Typen keinen gemeinsamen Wert, wird die Eigenschaft zu never, und der Compiler sagt nichts, bis du versuchst, einen Wert zu erzeugen:

index.ts(7,22): error TS2322: Type 'string' is not assignable to type 'never'.

Der Fehler zeigt auf das Objekt, nicht auf den Typ, der ihn verursacht hat, und deshalb sind diese Bugs mühsam zu verfolgen. Fährst du im Editor mit der Maus über r.id, siehst du seinen Typ: never. Ist die widersprüchliche Eigenschaft ein Literal-Tag, wie in type Shape = { kind: "circle" } & { kind: "square" }, geht TypeScript weiter und reduziert die ganze Intersection auf never. Das Lesen einer Eigenschaft eines solchen Werts erklärt dann den Grund: Property 'kind' does not exist on type 'never'. The intersection 'Shape' was reduced to 'never' because property 'kind' has conflicting types in some constituents.

interface ... extends findet denselben Konflikt stattdessen an der Deklaration, mit dem Fehler TS2430. Das ist der wichtigste praktische Unterschied zwischen beiden; die Seite interface vs type stellt sie nebeneinander.

Kompatible Überschneidungen engen die Eigenschaft ein

Überschneiden sich die beiden Eigenschaftstypen, ist das Ergebnis die Überschneidung. Das ist nützlich, kein Fehler:

Die zweite Hälfte zeigt, was & mit Unions macht: Es behält die Member, die beide Seiten gemeinsam haben. Wenn man Typen als Mengen von Werten betrachtet, wird das vorhersagbar. A | B ist die Vereinigung der beiden Mengen, A & B ihre Schnittmenge, und eine leere Schnittmenge ist never.

Intersection vs Union

Die Namen stammen aus der Mengenlehre und wirken auf Objekteigenschaften angewendet verkehrt herum:

A | B (Union)A & B (Intersection)
Ein Wert istein A oder ein Bein A und ein B
Menge erlaubter Wertegrößerkleiner
Verwendbare Eigenschaftennur die in beidenalle aus beiden
string mit numberstring | numbernever
"a" | "b" mit "b" | "c""a" | "b" | "c""b"

Eine Intersection von Objekttypen hat genau deshalb mehr Eigenschaften, weil sie weniger Werte erlaubt: nur Objekte, die alles haben.

Intersection vs extends

type C = A & Binterface C extends A, B
Funktioniert mitbeliebigen Typen, auch Unions und TypparameternObjekttypen mit statisch bekannten Membern
Widersprüchliche Eigenschaftwird stillschweigend zu neverFehler TS2430 oder TS2320 an der Deklaration
Ergebniseine Intersection, Bestandteil für Bestandteil geprüftein flacher benannter Typ, dessen Beziehungen zwischengespeichert werden
Große Zusammensetzungenkönnen die Typprüfung verlangsamenvon der Performance-Seite des TypeScript-Wikis bevorzugt

Zum Kombinieren einiger Typaliasse für Objekte ist & idiomatisch und in Ordnung. Für einen Typ aus vielen Teilen oder einen öffentlichen API-Typ liefert extends frühere Fehler und günstigere Typprüfungen.

Intersections mit primitiven Typen: Branding

Einen primitiven Typ mit einem Objekttyp zu schneiden ergibt kein never: string & { readonly __brand: "UserId" } ist ein String, der eine zusätzliche Markierung trägt, die nur beim Kompilieren existiert. Kein echter String hat diese Eigenschaft, und genau darum geht es: Nur Code, der die Marke absichtlich behauptet, kann einen solchen Wert erzeugen, also lässt sich ein einfacher string oder eine OrderId nicht mehr übergeben, wo eine UserId erwartet wird. Diese Technik hat eine eigene Seite, Branded Types.

Häufig gestellte Fragen

Was ist ein Intersection-Typ in TypeScript?

Ein als A & B geschriebener Typ, dessen Werte A und B zugleich erfüllen müssen. Bei Objekttypen heißt das, dass der Wert jede Eigenschaft von A und jede Eigenschaft von B hat. Das ist der übliche Weg, zwei Typaliasse zu einem zu kombinieren.

Was ist der Unterschied zwischen einer Union und einer Intersection?

Eine Union A | B bedeutet "eines von beiden": Der Wert kann ein A oder ein B sein, und du kannst nur verwenden, was sie gemeinsam haben, bis du einengst. Eine Intersection A & B bedeutet "beides": Der Wert hat alles von beiden. Bei Objekttypen akzeptiert die Union mehr Werte, und die Intersection hat mehr Eigenschaften.

Warum ist mein Intersection-Typ never?

Weil kein Wert beide Seiten erfüllen kann. string & number ist never, und { id: string } & { id: number } macht id zu string & number, also ist die Eigenschaft never, und es lässt sich kein Objekt erzeugen. Haben zwei Objekttypen dasselbe Literal-Tag mit unterschiedlichen Werten (kind: "circle" und kind: "square"), wird die ganze Intersection auf never reduziert.

Sollte ich eine Intersection oder extends verwenden?

Beides kombiniert Objekttypen. interface X extends A, B meldet widersprüchliche Eigenschaften an der Deklaration und wird vom TypeScript-Team für das Zusammensetzen großer Objekttypen empfohlen. & funktioniert mit jedem Typ, auch mit Unions und generischen Parametern, die extends nicht kombinieren kann. Nimm & für Typaliasse und generische Hilfstypen und extends beim Aufbau von Interfaces.

Wie führe ich in TypeScript zwei Objekttypen zusammen?

Schreibe type Merged = A & B. Für den Wert zur Laufzeit spreadest du beide Objekte: const merged: A & B = { ...a, ...b }. Teilen A und B eine Eigenschaft mit unterschiedlichen Typen, wird der Typ für diese Eigenschaft zu never; nimm Omit<A, keyof B> & B, wenn die Eigenschaften des zweiten Objekts die des ersten ersetzen sollen.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S