Menu

Types intersection en TypeScript : combiner des types avec &

Un type intersection A & B décrit une valeur qui est à la fois A et B, et qui possède donc tous les membres des deux. Apprenez à combiner des types objet avec &, pourquoi les propriétés contradictoires deviennent never, pourquoi l'intersection d'unions ne garde que les membres communs, et quand utiliser plutôt extends.

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

Un type intersection, écrit A & B, décrit une valeur qui est à la fois un A et un B. Pour les types objet, cela signifie que la valeur possède toutes les propriétés des deux. C'est ainsi qu'on combine des types existants sans réécrire leurs propriétés.

Le second objet omet team, il provoque donc l'erreur de compilation TS2322, et la ligne suivante du message indique Property 'team' is missing ... but required in type 'Employee'. Une valeur Staff peut être passée partout où un Person ou un Employee est attendu.

Combiner des types objet

& fonctionne avec n'importe quel mélange d'alias de type, d'interfaces et de types objet en ligne, ainsi qu'avec les paramètres de type génériques. C'est dans ce dernier cas qu'il est difficile à remplacer : une fonction qui ajoute des propriétés à l'objet qu'elle reçoit, quel qu'il soit, peut l'exprimer précisément.

L'appelant conserve le type exact de ce qu'il a passé (title, words), plus les deux propriétés ajoutées. Une interface ... extends ne peut pas exprimer « quel que soit T, plus ceci », car une interface ne peut pas étendre un paramètre de type.

Les propriétés contradictoires deviennent never

Quand les deux côtés déclarent la même propriété, le type de la propriété est l'intersection des deux. Si ces types n'ont aucune valeur en commun, la propriété devient never, et le compilateur ne dit rien tant que vous n'essayez pas de créer une valeur :

index.ts(7,22): error TS2322: Type 'string' is not assignable to type 'never'.

L'erreur désigne l'objet, pas le type qui l'a causée, ce qui rend ces bugs longs à retrouver. Survoler r.id dans l'éditeur montre son type : never. Quand la propriété contradictoire est une étiquette littérale, comme dans type Shape = { kind: "circle" } & { kind: "square" }, TypeScript va plus loin et réduit toute l'intersection à never. Lire une propriété d'une telle valeur explique alors pourquoi : Property 'kind' does not exist on type 'never'. The intersection 'Shape' was reduced to 'never' because property 'kind' has conflicting types in some constituents.

interface ... extends détecte le même conflit au niveau de la déclaration, avec l'erreur TS2430. C'est la principale différence pratique entre les deux ; la page interface ou type les compare côte à côte.

Les recouvrements compatibles affinent la propriété

Si les deux types de propriété se recouvrent, le résultat est ce recouvrement. C'est utile, pas une erreur :

La seconde moitié montre ce que & fait aux unions : il garde les membres communs aux deux côtés. Voir les types comme des ensembles de valeurs rend cela prévisible. A | B est la réunion des deux ensembles, A & B leur partie commune, et une partie commune vide vaut never.

Intersection ou union

Les noms viennent de la théorie des ensembles, et ils semblent inversés quand on les applique aux propriétés d'objets :

A | B (union)A & B (intersection)
Une valeur estun A ou un Bun A et un B
Ensemble des valeurs autoriséesplus grandplus petit
Propriétés utilisablesseulement celles présentes dans les deuxtoutes celles de l'un ou de l'autre
string avec numberstring | numbernever
"a" | "b" avec "b" | "c""a" | "b" | "c""b"

Une intersection de types objet a plus de propriétés précisément parce qu'elle autorise moins de valeurs : seulement les objets qui ont tout.

Intersection ou extends

type C = A & Binterface C extends A, B
Fonctionne avecn'importe quels types, y compris les unions et les paramètres de typeles types objet dont les membres sont connus statiquement
Propriété contradictoiredevient never en silenceerreur TS2430 ou TS2320 au niveau de la déclaration
Résultatune intersection, vérifiée constituant par constituantun seul type nommé et plat, dont les relations sont mises en cache
Grosses compositionspeuvent ralentir la vérification des typesrecommandé par la page Performance du wiki TypeScript

Pour combiner quelques alias de types objet, & est idiomatique et convient très bien. Pour un type construit à partir de nombreuses parties, ou un type d'API publique, extends donne des erreurs plus tôt et des vérifications moins coûteuses.

Intersections avec des primitives : le branding

L'intersection d'une primitive et d'un type objet ne donne pas never : string & { readonly __brand: "UserId" } est une chaîne qui porte un marqueur supplémentaire, présent uniquement à la compilation. Aucune vraie chaîne n'a cette propriété, et c'est justement le but : seul du code qui affirme délibérément la marque peut en créer une, si bien qu'un simple string ou un OrderId ne peut plus être passé là où un UserId est attendu. Cette technique a sa propre page, types branded.

Questions fréquentes

Qu'est-ce qu'un type intersection en TypeScript ?

Un type écrit A & B dont les valeurs doivent satisfaire à la fois A et B. Pour les types objet, cela signifie que la valeur possède toutes les propriétés de A et toutes celles de B. C'est la façon habituelle de combiner deux alias de type en un seul.

Quelle est la différence entre une union et une intersection ?

Une union A | B signifie « l'un ou l'autre » : la valeur peut être un A ou un B, et vous ne pouvez utiliser que ce qu'ils partagent jusqu'à ce que vous affiniez. Une intersection A & B signifie « les deux » : la valeur possède tout ce qu'ont les deux. Avec des types objet, l'union accepte plus de valeurs et l'intersection a plus de propriétés.

Pourquoi mon type intersection vaut-il never ?

Parce qu'aucune valeur ne peut satisfaire les deux côtés. string & number vaut never, et { id: string } & { id: number } fait de id un string & number : la propriété vaut never et aucun objet ne peut être créé. Si deux types objet ont la même étiquette littérale avec des valeurs différentes (kind: "circle" et kind: "square"), toute l'intersection se réduit à never.

Faut-il utiliser une intersection ou extends ?

Les deux combinent des types objet. interface X extends A, B signale les propriétés contradictoires au niveau de la déclaration et est recommandé par l'équipe TypeScript pour composer de gros types objet. & fonctionne avec n'importe quel type, y compris les unions et les paramètres génériques, que extends ne peut pas combiner. Utilisez & pour les alias de type et les helpers génériques, et extends pour construire des interfaces.

Comment fusionner deux types objet en TypeScript ?

Écrivez type Merged = A & B. Pour la valeur à l'exécution, étalez les deux objets : const merged: A & B = { ...a, ...b }. Si A et B partagent une propriété avec des types différents, le type de cette propriété devient never ; utilisez Omit<A, keyof B> & B quand les propriétés du second objet doivent remplacer celles du premier.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER