Exclude<T, U> retire des membres d'un type union, Extract<T, U> ne garde que les membres qui correspondent, et NonNullable<T> retire null et undefined. Tous trois filtrent des unions ; ils ne touchent pas aux propriétés des objets.
Comme tous les utility types, ils n'existent qu'à la compilation. La dernière ligne est refusée par le compilateur ; le JavaScript exécuté ignore tout de Visible.
Exclude, Extract et NonNullable en un coup d'œil
| Type | Garde | Exemple | Résultat |
|---|---|---|---|
Exclude<T, U> | les membres de T non affectables à U | Exclude<"a" | "b" | "c", "a"> | "b" | "c" |
Extract<T, U> | les membres de T affectables à U | Extract<"a" | "b" | "c", "a" | "z"> | "a" |
NonNullable<T> | tout sauf null et undefined | NonNullable<string | null | undefined> | string |
« Affectable à U » est l'expression clé. U n'a pas besoin d'être une liste de membres exacts ; ce peut être un type plus large auquel plusieurs membres correspondent :
Sélectionner des membres d'une union par leur forme
Sur une union discriminée, Extract avec une forme partielle récupère les variantes qui ont cette forme. C'est la façon habituelle de nommer une variante d'une union que vous n'avez pas écrite vous-même :
La forme { type: "click" } est un supertype de la seule variante click, c'est donc celle que Extract garde. Une forme qui correspond à plusieurs variantes (par exemple { x: number } si deux d'entre elles l'avaient) les garde toutes.
NonNullable
NonNullable<T> est la version type d'une vérification !== null && !== undefined. On le rencontre quand on dérive un type de quelque chose d'optionnel :
User["manager"] vaut { name: string } | null | undefined (le ? ajoute undefined), et NonNullable retire les deux. Depuis TypeScript 5.5, le compilateur infère aussi ce prédicat tout seul, donc raw.filter((s) => s != null) est déjà typé string[] ; le s is NonNullable<typeof s> explicite rend l'intention visible et fonctionne dans les versions plus anciennes. Sa définition est type NonNullable<T> = T & {} : le type objet vide {} accepte toutes les valeurs sauf null et undefined, donc l'intersection avec lui retire exactement ces deux-là.
Comment ils sont construits
Exclude et Extract sont des types conditionnels d'une ligne dans la bibliothèque standard :
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
Quand T est une union, un type conditionnel est appliqué à chaque membre séparément (il se distribue), et les membres never disparaissent du résultat. Ainsi Exclude<"a" | "b", "a"> devient ("a" extends "a" ? never : "a") | ("b" extends "a" ? never : "b"), soit never | "b", soit "b". La page sur les types conditionnels détaille la distribution.
Exclude vs Omit, Extract vs Pick
Les noms se confondent facilement, car les deux paires « retirent » ou « gardent » quelque chose. La différence tient à ce sur quoi ils agissent :
| Agit sur | Retire ou garde | Exemple | |
|---|---|---|---|
Exclude<T, U> | une union | des membres de l'union | Exclude<"a" | "b", "a"> → "b" |
Extract<T, U> | une union | des membres de l'union | Extract<"a" | "b", "a"> → "a" |
Omit<T, K> | un type objet | des propriétés | Omit<User, "password"> |
Pick<T, K> | un type objet | des propriétés | Pick<User, "name"> |
Utiliser Exclude sur un type objet est une erreur courante, et elle passe inaperçue :
Account n'est pas affectable à "password", donc Exclude le garde en entier. Omit lui-même est construit à partir de Exclude appliqué aux clés : Omit<T, K> vaut Pick<T, Exclude<keyof T, K>>. Voir Omit pour plus de détails.
Un Exclude plus strict
Le second paramètre de Exclude accepte n'importe quel type, donc une faute de frappe ne retire rien et ne lève aucune erreur. Ajouter une contrainte oblige le compilateur à vérifier que ce que vous excluez fait vraiment partie de l'union :
index.ts(7,37): error TS2344: Type '"drafft"' does not satisfy the constraint 'Status'.
Corrigez l'orthographe et le code compile, avec Strict égal à "published" | "archived". La même astuce fonctionne pour Extract et pour un Omit strict (K extends keyof T).
Questions fréquentes
À quoi sert Exclude en TypeScript ?
Exclude<T, U> retire de l'union T chaque membre affectable à U. Exclude<"a" | "b" | "c", "a"> vaut "b" | "c". Pour retirer plusieurs membres, passez une union comme U : Exclude<T, "a" | "b">.
Quelle est la différence entre Exclude et Extract ?
Ils sont opposés. Exclude<T, U> garde les membres de T qui ne sont pas affectables à U ; Extract<T, U> ne garde que ceux qui le sont. Pour les mêmes T et U, les deux résultats réunis reconstituent T.
Quelle est la différence entre Exclude et Omit ?
Exclude agit sur un type union et retire des membres de l'union. Omit agit sur un type objet et retire des propriétés. Exclude<User, "password"> ne fait rien d'utile (il renvoie User), alors que Omit<User, "password"> donne le type objet sans cette propriété. Omit est construit sur Exclude : il exclut la clé de keyof T.
À quoi sert NonNullable en TypeScript ?
NonNullable<T> retire null et undefined d'un type : NonNullable<string | null | undefined> vaut string. Il est défini comme T & {}, puisque {} accepte toutes les valeurs sauf null et undefined.
Pourquoi Exclude ne signale-t-il pas d'erreur pour un membre qui n'existe pas ?
Son second paramètre n'est pas contraint, donc n'importe quel type est accepté, et une faute de frappe comme Exclude<Status, "drafft"> ne retire rien sans rien dire. Définissez une version plus stricte, type StrictExclude<T, U extends T> = Exclude<T, U>, pour que le compilateur vérifie que U fait partie de T.