Menu

TypeScript Union Types: der Operator | mit Beispielen

Ein Union-Typ wie string | number bedeutet, dass ein Wert einer von mehreren Typen sein kann. Was du mit einer Union tun kannst (nur das, was jeder Member unterstützt), wie du sie einengst, Unions aus Literalen und Objekttypen und der Unterschied zwischen (A | B)[] und A[] | B[].

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

Ein Union-Typ listet Alternativen mit | auf: Ein Wert vom Typ string | number ist entweder ein String oder eine Zahl. Mit Unions beschreibt TypeScript Werte, die berechtigterweise mehr als eine Form annehmen können, und der Compiler zwingt dich zu prüfen, welche Form du hast, bevor du etwas Formspezifisches verwendest.

In jedem Zweig der Prüfung mit typeof hat id einen einzigen Typ. Dieser Schritt heißt Narrowing, und er macht Unions praktisch nutzbar.

Nur gemeinsame Member sind erlaubt

Vor dem Einengen kannst du nur verwenden, was jeder Member der Union unterstützt. toString() gibt es auf Strings und Zahlen, das ist also in Ordnung; toUpperCase() gibt es nur auf Strings:

index.ts(3,16): error TS2339: Property 'toUpperCase' does not exist on type 'string | number'.
  Property 'toUpperCase' does not exist on type 'number'.

Die zweite Zeile nennt den Member, dem die Eigenschaft fehlt. Dieselbe Regel gilt in die andere Richtung: Ein Wert vom Typ string | number kann nicht an einen als string typisierten Parameter übergeben werden (TS2345), weil er eine Zahl sein könnte. Eine Union akzeptiert mehr Werte und lässt dich dafür weniger mit ihnen tun, bis du prüfst.

Eine Union einengen

Narrowing nutzt gewöhnliche JavaScript-Prüfungen. TypeScript folgt dem Kontrollfluss und entfernt Member, sobald sie ausgeschlossen sind, sodass nach der letzten Prüfung nur ein Member übrig bleibt:

PrüfungEngt einGeeignet für
typeof x === "string"auf den primitiven Typstring, number, boolean, bigint, symbol, undefined, function
x === null, x === "a"auf den verglichenen Wertnull, undefined, Literal-Member
Array.isArray(x)auf den Array-MemberArrays
x instanceof Dateauf die KlasseKlasseninstanzen
"meow" in xauf Member, die die Eigenschaft habenObjekttypen
x.kind === "circle"auf den Member mit diesem TagDiscriminated Unions
isCat(x) (gibt x is Cat zurück)auf das, was die Funktion sagtalles, eigene Logik

Die vollständige Liste der Formen des Narrowing steht auf der Seite zu Type Narrowing.

Unions aus Literaltypen

Eine Union aus Literalwerten ist eine geschlossene Menge erlaubter Werte. Sie ist die häufigste Union in echtem Code:

type Status = "idle" | "loading" | "success" | "error";
type Dice = 1 | 2 | 3 | 4 | 5 | 6;
type Toggle = "on" | "off" | boolean; // boolean is itself true | false

let current: Status = "idle";
current = "loading";   // fine
current = "finished";  // error TS2322: Type '"finished"' is not assignable to type 'Status'.

Der Vergleich mit einem Literal engt ein: Nach if (current === "error") weiß der else-Zweig, dass current einer der anderen drei ist. Literal-Unions ersetzen in vielen Codebasen Enums; unter Literaltypen stehen as const und wie man eine solche Union aus einem Array ableitet.

Unions aus Objekttypen

Sind die Member Objekttypen, sind Eigenschaften, die alle teilen, direkt verfügbar. Für den Rest prüfst du mit in, ob die Eigenschaft existiert:

Bei Unions aus mehreren Objektformen ist ein gemeinsames Literal-Feld wie kind: "cat" / kind: "fish" das sauberere Muster. Die Prüfung dieser einen Eigenschaft engt das ganze Objekt ein, und ein switch darüber lässt sich auf Vollständigkeit prüfen. Dieses Muster ist eine Discriminated Union.

Arrays und Unions

Wo die Klammern stehen, ändert die Bedeutung komplett:

TypBedeutetBeispielwert
(string | number)[]ein Array, dessen Elemente jeweils ein String oder eine Zahl sind[1, "two", 3]
string[] | number[]ein Array nur aus Strings oder ein Array nur aus Zahlen["a", "b"]
string | number[]ein String oder ein Array von Zahlen (| bindet schwächer als [])"text"

Beim Durchlaufen eines (string | number)[] ist jedes Element die Union und muss eingeengt werden, wie im Callback von reduce oben. Methoden wie map und filter funktionieren auch auf einem string[] | number[], wobei der Callback string | number bekommt.

Unions mit null und undefined

Die häufigste Union überhaupt ist „ein Wert oder nichts“: string | null, User | undefined. Das geben Array.prototype.find und Map.prototype.get zurück, und eine optionale Eigenschaft name?: string liest sich als string | undefined. Wie du sie mit ?., ?? und Null-Prüfungen behandelst, steht auf einer eigenen Seite: null und undefined.

Um Member auf Typebene aus einer vorhandenen Union zu entfernen, nimm die eingebauten Utilities: Exclude<"a" | "b" | "c", "a"> ist "b" | "c", und NonNullable<string | null> ist string.

Häufig gestellte Fragen

Was ist ein Union-Typ in TypeScript?

Ein Typ aus mehreren Alternativen, verbunden mit |. Ein Wert vom Typ string | number kann ein String oder eine Zahl sein. Der Compiler lässt dich nur verwenden, was alle Member gemeinsam haben, bis du den Wert mit einer Prüfung wie typeof value === "string" auf einen Member einengst.

Warum sagt TypeScript, eine Eigenschaft existiere auf einem Union-Typ nicht?

Weil mindestens einem Member der Union sie fehlt. Der Fehler TS2339, zum Beispiel Property 'toUpperCase' does not exist on type 'string | number', bedeutet, dass der Wert eine Zahl sein könnte, und die hat kein toUpperCase. Enge zuerst ein (typeof, in, Array.isArray, instanceof oder eine Prüfung auf die Diskriminante) und verwende dann die Eigenschaft des jeweiligen Members.

Wie deklariere ich ein Array, das mehr als einen Typ enthält?

Setze die Union in Klammern: (string | number)[] oder Array<string | number>, wobei jedes Element einer der beiden Typen sein kann. string[] | number[] ist etwas anderes: Das ganze Array besteht nur aus Strings oder nur aus Zahlen. Ohne Klammern bedeutet string | number[] einen String oder ein Array von Zahlen.

Was ist der Unterschied zwischen einem Union- und einem Intersection-Typ?

Eine Union A | B ist ein Wert, der einer der Typen ist, du kannst also nur verwenden, was sie gemeinsam haben. Eine Intersection A & B ist ein Wert, der beides zugleich ist, also hat sie alle Member beider. Bei Objekttypen akzeptiert A | B mehr Werte und A & B verlangt mehr Eigenschaften.

Wie prüfe ich, welcher Typ ein Union-Wert ist?

Mit einer Prüfung zur Laufzeit, die TypeScript versteht: typeof x === "string" für primitive Werte, Array.isArray(x) für Arrays, x instanceof Date für Klassen, "prop" in x für Objektformen oder x.kind === "circle", wenn die Member ein gemeinsames Literal-Tag haben. Für eigene Logik schreibst du eine Type-Guard-Funktion, die x is T zurückgibt.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S