ProgrammationDéveloppeur Fullstack

Comment fonctionne Exclude en TypeScript, quand l'utiliser pour des manipulations avec des types union, et quelles sont les nuances lors de l'utilisation de ce type utilitaire ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Exclude<T, U> est un type utilitaire introduit dans TypeScript pour soustraire un type d'un autre, lorsque certains valeurs doivent être exclues d'un type union.

Contexte

Au départ, TypeScript ne proposait pas de méthode pratique pour soustraire un type d'un autre. Lors de la création d'API génériques et de refactorisations, il était souvent nécessaire d'obtenir un type "restant" — tout sauf les valeurs interdites. Plutôt que de manipuler manuellement les unions, il fallait maintenir plusieurs interfaces similaires.

Problème

Par exemple, lorsqu'il y a un type 'A | B | C', mais qu'il est nécessaire d'obtenir un type sans B. Cela est fréquemment requis lors de la construction de paramètres d'entrée complexes pour des fonctions, la filtration des valeurs autorisées et la formation dynamique des types.

Solution

Exclude résout ce problème. Sa signature simplifiée est la suivante :

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

Il retourne un type excluant de T tous les membres de U.

Exemple :

type Status = 'draft' | 'published' | 'removed'; type UserVisibleStatus = Exclude<Status, 'removed'>; const visible: UserVisibleStatus = 'draft'; // OK

Caractéristiques clés :

  • Permet de former des types dynamiques grâce à la "soustraction" de parties d'une union.
  • Facilite le refactoring — lorsqu'un type de base change, tous les dérivés se mettent à jour automatiquement.
  • Peut être utilisé, par exemple, pour filtrer les cas de switch ou les clés d'objet.

Questions pièges.

Peut-on utiliser Exclude pour des types ordinaires, et non des unions ?

Si T n'est pas un type union, mais fait partie de U — Exclude fonctionnera toujours, mais le résultat peut être never ou T, ce qui n'est pas toujours intuitif.

Exclude<'a', 'a'> // résultat : never Exclude<'a', 'b'> // résultat : 'a'

Exclude supprime-t-il toutes les mentions d'un type dans la structure d'un objet ?

Non, Exclude ne passe pas récursivement à travers les champs imbriqués du type, il exclut uniquement au niveau supérieur de l'union.

Comment fonctionne Exclude avec des interfaces et des types d'objets ?

Il compare l'entièreté du type, et non des propriétés individuelles. Ainsi, Exclude d'une union de plusieurs interfaces ne supprime que celles qui correspondent exactement à U.

interface A { x: number }; interface B { y: string }; // Exclude<A|B, B> donne : A (B correspond exactement)

Erreurs typiques et anti-patterns

  • Essayer d'appliquer Exclude pour des correspondances imbriquées ou partielles.
  • Utilisation pour "supprimer" des propriétés d'interface, et non des variantes d'union.
  • Ignorer la possibilité d'obtenir un type never lors d'une correspondance complète des types.

Exemple de la vie réelle

Cas négatif

Validation des rôles d'utilisateur via Exclude<UserRoles, 'admin'>, mais oublié que Exclude ne s'applique pas aux structures imbriquées — les droits 'admin:sub' n'ont pas été exclus.

Avantages :

  • Simplicité de la formation de types de rôles.

Inconvénients :

  • Comportement peu évident avec des types imbriqués ou similaires ; un rôle critique a été omis.

Cas positif

Utilisation d'Exclude pour restreindre le API public aux actions : Exclude<Action, 'delete'>, ce qui exclut l'opération dangereuse.

Avantages :

  • Sécurité au niveau de la typage, impossible d'appeler une action interdite.

Inconvénients :

  • Nécessite de maintenir des listes de types de base à jour.