Menu

Exclude, Extract et NonNullable en TypeScript expliqués

Exclude, Extract et NonNullable filtrent les membres d'un type union. Découvrez ce que chacun garde et retire, comment sélectionner des membres d'une union par leur forme, comment ils sont construits à partir de types conditionnels, et en quoi ils diffèrent de Omit et Pick.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

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

TypeGardeExempleRésultat
Exclude<T, U>les membres de T non affectables à UExclude<"a" | "b" | "c", "a">"b" | "c"
Extract<T, U>les membres de T affectables à UExtract<"a" | "b" | "c", "a" | "z">"a"
NonNullable<T>tout sauf null et undefinedNonNullable<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 surRetire ou gardeExemple
Exclude<T, U>une uniondes membres de l'unionExclude<"a" | "b", "a"> → "b"
Extract<T, U>une uniondes membres de l'unionExtract<"a" | "b", "a"> → "a"
Omit<T, K>un type objetdes propriétésOmit<User, "password">
Pick<T, K>un type objetdes propriétésPick<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.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER