Menu

TypeScript Exclude, Extract und NonNullable erklärt

Exclude, Extract und NonNullable filtern die Member eines Union-Typs. Was jeder behält und entfernt, wie du Union-Member nach ihrer Form auswählst, wie sie aus Conditional Types gebaut sind und wie sie sich von Omit und Pick unterscheiden.

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

Exclude<T, U> entfernt Member aus einem Union-Typ, Extract<T, U> behält nur die passenden Member, und NonNullable<T> entfernt null und undefined. Alle drei filtern Unions; Objekteigenschaften rühren sie nicht an.

Wie jeder Utility Type existieren sie nur beim Kompilieren. Die letzte Zeile lehnt der Compiler ab; das JavaScript, das läuft, hat keine Ahnung, was Visible ist.

Exclude, Extract und NonNullable im Überblick

TypBehältBeispielErgebnis
Exclude<T, U>Member von T, die U nicht zuweisbar sindExclude<"a" | "b" | "c", "a">"b" | "c"
Extract<T, U>Member von T, die U zuweisbar sindExtract<"a" | "b" | "c", "a" | "z">"a"
NonNullable<T>alles außer null und undefinedNonNullable<string | null | undefined>string

„U zuweisbar“ ist die entscheidende Formulierung. U muss keine Liste exakter Member sein; es kann ein weiterer Typ sein, zu dem mehrere Member passen:

Union-Member nach ihrer Form auswählen

Bei einer Discriminated Union holt Extract mit einer Teilform die Varianten heraus, die sie haben. So benennt man üblicherweise eine Variante einer Union, die man nicht selbst geschrieben hat:

Die Form { type: "click" } ist nur ein Obertyp der Click-Variante, also behält Extract genau diese. Eine Form, die zu mehreren Varianten passt (etwa { x: number }, wenn zwei es hätten), behält alle.

NonNullable

NonNullable<T> ist die Typversion einer Prüfung !== null && !== undefined. Es taucht auf, wenn du einen Typ aus etwas Optionalem ableitest:

User["manager"] ist { name: string } | null | undefined (das ? fügt undefined hinzu), und NonNullable entfernt beides. Seit TypeScript 5.5 leitet der Compiler dieses Predicate auch selbst ab, also ist raw.filter((s) => s != null) bereits als string[] typisiert; das ausdrückliche s is NonNullable<typeof s> hält die Absicht sichtbar und funktioniert in älteren Versionen. Die Definition ist type NonNullable<T> = T & {}: Der leere Objekttyp {} akzeptiert jeden Wert außer null und undefined, die Schnittmenge damit entfernt also genau diese.

Wie sie gebaut sind

Exclude und Extract sind einzeilige Conditional Types in der Standardbibliothek:

type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;

Ist T eine Union, wird ein Conditional Type auf jeden Member einzeln angewendet (er distribuiert), und Member mit never verschwinden aus dem Ergebnis. Also wird Exclude<"a" | "b", "a"> zu ("a" extends "a" ? never : "a") | ("b" extends "a" ? never : "b"), also never | "b", also "b". Die Seite zu Conditional Types behandelt die Distribution im Detail.

Exclude vs Omit, Extract vs Pick

Die Namen verwechselt man leicht, weil beide Paare etwas „entfernen“ oder „behalten“. Der Unterschied ist, worauf sie arbeiten:

Arbeitet aufEntfernt oder behältBeispiel
Exclude<T, U>einer UnionUnion-MemberExclude<"a" | "b", "a"> → "b"
Extract<T, U>einer UnionUnion-MemberExtract<"a" | "b", "a"> → "a"
Omit<T, K>einem ObjekttypEigenschaftenOmit<User, "password">
Pick<T, K>einem ObjekttypEigenschaftenPick<User, "name">

Exclude auf einen Objekttyp anzuwenden ist ein häufiger Ausrutscher, und er scheitert still:

Account ist "password" nicht zuweisbar, also behält Exclude es vollständig. Omit selbst ist aus Exclude auf den Schlüsseln gebaut: Omit<T, K> ist Pick<T, Exclude<keyof T, K>>. Mehr dazu unter Omit.

Ein strengeres Exclude

Der zweite Parameter von Exclude akzeptiert jeden Typ, ein Tippfehler entfernt also nichts und löst keinen Fehler aus. Ein Constraint lässt den Compiler prüfen, dass das Ausgeschlossene wirklich in der Union liegt:

index.ts(7,37): error TS2344: Type '"drafft"' does not satisfy the constraint 'Status'.

Korrigiere die Schreibweise, dann kompiliert es, und Strict ist gleich "published" | "archived". Derselbe Trick funktioniert für Extract und für ein strenges Omit (K extends keyof T).

Häufig gestellte Fragen

Was macht Exclude in TypeScript?

Exclude<T, U> entfernt aus der Union T jeden Member, der U zuweisbar ist. Exclude<"a" | "b" | "c", "a"> ist "b" | "c". Um mehrere Member zu entfernen, übergib eine Union als U: Exclude<T, "a" | "b">.

Was ist der Unterschied zwischen Exclude und Extract?

Sie sind Gegenstücke. Exclude<T, U> behält die Member von T, die U nicht zuweisbar sind; Extract<T, U> behält nur die, die es sind. Für dasselbe T und U ergeben beide Ergebnisse zusammen T.

Was ist der Unterschied zwischen Exclude und Omit?

Exclude arbeitet auf einem Union-Typ und entfernt Union-Member. Omit arbeitet auf einem Objekttyp und entfernt Eigenschaften. Exclude<User, "password"> bewirkt nichts Nützliches (es gibt User zurück), während Omit<User, "password"> den Objekttyp ohne diese Eigenschaft liefert. Omit baut auf Exclude auf: Es schließt den Schlüssel aus keyof T aus.

Was macht NonNullable in TypeScript?

NonNullable<T> entfernt null und undefined aus einem Typ: NonNullable<string | null | undefined> ist string. Es ist als T & {} definiert, da {} jeden Wert außer null und undefined akzeptiert.

Warum meldet Exclude keinen Fehler für einen Member, den es nicht gibt?

Sein zweiter Parameter hat keinen Constraint, jeder Typ wird also akzeptiert, und ein Tippfehler wie Exclude<Status, "drafft"> entfernt stillschweigend nichts. Definiere eine strengere Version, type StrictExclude<T, U extends T> = Exclude<T, U>, damit der Compiler prüft, dass U Teil von T ist.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S