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
| Typ | Behält | Beispiel | Ergebnis |
|---|---|---|---|
Exclude<T, U> | Member von T, die U nicht zuweisbar sind | Exclude<"a" | "b" | "c", "a"> | "b" | "c" |
Extract<T, U> | Member von T, die U zuweisbar sind | Extract<"a" | "b" | "c", "a" | "z"> | "a" |
NonNullable<T> | alles außer null und undefined | NonNullable<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 auf | Entfernt oder behält | Beispiel | |
|---|---|---|---|
Exclude<T, U> | einer Union | Union-Member | Exclude<"a" | "b", "a"> → "b" |
Extract<T, U> | einer Union | Union-Member | Extract<"a" | "b", "a"> → "a" |
Omit<T, K> | einem Objekttyp | Eigenschaften | Omit<User, "password"> |
Pick<T, K> | einem Objekttyp | Eigenschaften | Pick<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.