Un type union liste des alternatives avec | : une valeur de type string | number est soit une chaîne, soit un nombre. Les unions sont la façon dont TypeScript décrit les valeurs qui peuvent légitimement prendre plusieurs formes, et le compilateur vous oblige à vérifier quelle forme vous avez avant d'utiliser quoi que ce soit qui lui soit propre.
Dans chaque branche de la vérification typeof, id n'a qu'un seul type. Cette étape s'appelle le narrowing, et c'est elle qui rend les unions utilisables.
Seuls les membres communs sont autorisés
Avant le narrowing, vous ne pouvez utiliser que ce que chaque membre de l'union permet. toString() existe sur les chaînes comme sur les nombres, il ne pose donc pas de problème ; toUpperCase() n'existe que sur les chaînes :
index.ts(3,16): error TS2339: Property 'toUpperCase' does not exist on type 'string | number'.
Property 'toUpperCase' does not exist on type 'number'.
La seconde ligne nomme le membre auquel manque la propriété. La même règle s'applique dans l'autre sens : une valeur string | number ne peut pas être passée à un paramètre typé string (TS2345), car ce pourrait être un nombre. Une union accepte plus de valeurs et, en contrepartie, vous laisse faire moins de choses avec elles tant que vous n'avez pas vérifié.
Affiner une union
Le narrowing utilise des vérifications JavaScript ordinaires. TypeScript suit le flux de contrôle et retire les membres à mesure qu'ils sont écartés, si bien qu'après la dernière vérification il ne reste qu'un membre :
| Vérification | Affine | Adaptée à |
|---|---|---|
typeof x === "string" | vers la primitive | string, number, boolean, bigint, symbol, undefined, function |
x === null, x === "a" | vers la valeur comparée | null, undefined, les membres littéraux |
Array.isArray(x) | vers le membre tableau | les tableaux |
x instanceof Date | vers la classe | les instances de classes |
"meow" in x | vers les membres qui ont la propriété | les types objet |
x.kind === "circle" | vers le membre portant cette étiquette | les unions discriminées |
isCat(x) (renvoie x is Cat) | vers ce que dit la fonction | tout, logique personnalisée |
La liste complète des formes de narrowing se trouve sur la page consacrée au narrowing.
Unions de types littéraux
Une union de valeurs littérales est un ensemble fermé de valeurs autorisées. C'est l'union la plus courante dans du vrai code :
type Status = "idle" | "loading" | "success" | "error";
type Dice = 1 | 2 | 3 | 4 | 5 | 6;
type Toggle = "on" | "off" | boolean; // boolean is itself true | false
let current: Status = "idle";
current = "loading"; // fine
current = "finished"; // error TS2322: Type '"finished"' is not assignable to type 'Status'.
Comparer à un littéral affine : après if (current === "error"), la branche else sait que current est l'une des trois autres valeurs. Les unions de littéraux remplacent les enums dans beaucoup de bases de code ; voir types littéraux pour as const et la façon de dériver une telle union d'un tableau.
Unions de types objet
Quand les membres sont des types objet, les propriétés qu'ils partagent tous sont directement disponibles. Pour les autres, vérifiez que la propriété existe avec in :
Pour les unions de plusieurs formes d'objets, le motif le plus propre est une propriété littérale commune comme kind: "cat" / kind: "fish". Vérifier cette seule propriété affine tout l'objet, et un switch sur elle peut être vérifié pour l'exhaustivité. Ce motif s'appelle une union discriminée.
Tableaux et unions
L'emplacement des parenthèses change complètement le sens :
| Type | Signifie | Exemple de valeur |
|---|---|---|
(string | number)[] | un tableau dont chaque élément est une chaîne ou un nombre | [1, "two", 3] |
string[] | number[] | un tableau de chaînes uniquement, ou un tableau de nombres uniquement | ["a", "b"] |
string | number[] | une chaîne, ou un tableau de nombres (| a une priorité plus faible que []) | "text" |
En parcourant un (string | number)[], chaque élément est l'union et doit être affiné, comme dans le callback de reduce ci-dessus. Des méthodes comme map et filter fonctionnent aussi sur un string[] | number[], le callback recevant alors string | number.
Unions avec null et undefined
L'union la plus courante de toutes est « une valeur ou rien » : string | null, User | undefined. C'est ce que renvoient Array.prototype.find et Map.prototype.get, et une propriété optionnelle name?: string se lit comme string | undefined. Traiter ces cas avec ?., ?? et des vérifications de null a sa propre page : null et undefined.
Pour retirer des membres d'une union existante au niveau des types, utilisez les utilitaires intégrés : Exclude<"a" | "b" | "c", "a"> vaut "b" | "c", et NonNullable<string | null> vaut string.
Questions fréquentes
Qu'est-ce qu'un type union en TypeScript ?
Un type composé de plusieurs alternatives reliées par |. Une valeur de type string | number peut être une chaîne ou un nombre. Le compilateur ne vous laisse utiliser que ce que tous les membres ont en commun, jusqu'à ce que vous affiniez la valeur vers un seul membre avec une vérification comme typeof value === "string".
Pourquoi TypeScript dit-il qu'une propriété n'existe pas sur un type union ?
Parce qu'au moins un membre de l'union ne la possède pas. L'erreur TS2339, par exemple Property 'toUpperCase' does not exist on type 'string | number', signifie que la valeur peut être un nombre, qui n'a pas de toUpperCase. Affinez d'abord (typeof, in, Array.isArray, instanceof, ou une vérification de discriminant), puis utilisez la propriété propre au membre.
Comment déclarer un tableau qui contient plus d'un type ?
Placez l'union entre parenthèses : (string | number)[] ou Array<string | number>, où chaque élément peut être de l'un ou l'autre type. string[] | number[] est différent : le tableau entier ne contient que des chaînes ou que des nombres. Sans parenthèses, string | number[] signifie une chaîne ou un tableau de nombres.
Quelle est la différence entre un type union et un type intersection ?
Une union A | B est une valeur qui est l'un des types, vous ne pouvez donc utiliser que ce qu'ils partagent. Une intersection A & B est une valeur qui est les deux à la fois, elle possède donc tous les membres des deux. Pour les types objet, A | B accepte plus de valeurs et A & B exige plus de propriétés.
Comment savoir de quel type est une valeur d'union ?
Utilisez une vérification à l'exécution que TypeScript comprend : typeof x === "string" pour les primitives, Array.isArray(x) pour les tableaux, x instanceof Date pour les classes, "prop" in x pour les formes d'objets, ou x.kind === "circle" quand les membres partagent une étiquette littérale. Pour une logique personnalisée, écrivez une fonction type guard qui renvoie x is T.