Menu

Types mappés en TypeScript : syntaxe, modificateurs et remappage

Un type mappé construit un nouveau type objet en parcourant des clés : { [K in keyof T]: ... }. Découvrez la syntaxe, les modificateurs readonly et ? avec + et -, le remappage des clés avec as, le filtrage des clés, et comment sont écrits Partial, Readonly, Required, Pick et Record.

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

Un type mappé construit un nouveau type objet en parcourant un ensemble de clés. { [K in keyof T]: boolean } signifie « pour chaque clé K de T, une propriété nommée K de type boolean » :

Ajoutez une clé à Features et Flags<Features> l'exige aussi. Les types mappés n'existent qu'à la compilation : ils décrivent des objets, ils n'en créent pas.

La syntaxe

Un type mappé a trois parties : une variable de clé, une union de clés à parcourir, et le type de la propriété, qui peut utiliser la variable de clé.

type MappedType = {
  [K in Keys]: PropertyType; // K takes each member of Keys in turn
};

Keys peut être n'importe quelle union de chaînes, de nombres ou de symboles. Elle n'a pas besoin de venir de keyof :

C'est exactement ce que fait Record<Size, number> ; Record est un type mappé d'une ligne. Le type de la propriété peut utiliser la clé : { [K in keyof T]: T[K] } recopie chaque type de propriété via un accès indexé, et { [K in keyof T]: T[K] | null } rend chaque propriété nullable.

Modificateurs : readonly et ? avec + et -

Un type mappé peut ajouter ou retirer d'un coup les modificateurs readonly et optionnel (?) sur chaque propriété. Préfixez + pour ajouter (le comportement par défaut quand vous n'écrivez rien) et - pour retirer :

La dernière ligne montre que readonly n'est qu'une règle de compilation : l'assignation a été signalée (et ici neutralisée avec @ts-expect-error), mais le JavaScript émis l'a quand même exécutée. -? fait plus que retirer le point d'interrogation : il retire aussi undefined du type de la propriété, si bien que AllRequired<Account> refuse { id: 1, email: undefined } avec error TS2322: Type 'undefined' is not assignable to type 'string'.

Comment sont construits Partial, Readonly, Pick et Record

Les types utilitaires intégrés qui remodèlent les objets sont des types mappés. Voici leurs définitions tirées du lib.es5.d.ts de TypeScript lui-même :

type Partial<T> = { [P in keyof T]?: T[P] };
type Required<T> = { [P in keyof T]-?: T[P] };
type Readonly<T> = { readonly [P in keyof T]: T[P] };
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
type Record<K extends keyof any, T> = { [P in K]: T };

Les lire est un bon exercice : Pick ne parcourt que les clés que vous passez, et Record ignore tout type source et donne à chaque clé le même type de valeur. Omit n'est pas un type mappé à part entière : c'est Pick<T, Exclude<keyof T, K>>. La référence des types utilitaires les liste tous.

Remappage des clés avec as

Une clause as après la clé change le nom de la propriété. Combinée aux template literal types, elle peut générer de nouveaux noms à partir des anciens :

string & K est là parce que keyof T peut inclure des clés number et symbol, et Capitalize n'accepte que des chaînes. L'intersection garde les clés de type string et écarte le reste.

Filtrer les clés avec never

Si la clause as produit never pour une clé, cette clé est supprimée. Avec un type conditionnel, vous pouvez garder ou écarter des propriétés selon leur type :

Des types comme DataOnly servent à décrire ce qui survit à JSON.stringify ou ce qu'un formulaire modifie : les champs, sans les méthodes.

Quels modificateurs sont conservés

Un type mappé sur keyof T pour un type T est dit homomorphe, et il recopie les modificateurs readonly et ? de chaque propriété de T. Un type mappé sur une simple union de clés commence sans aucun modificateur :

type Account = { readonly id: number; email?: string };

type Copy<T> = { [K in keyof T]: T[K] };
type A = Copy<Account>;
// { readonly id: number; email?: string }  (modifiers kept)

type B = { [K in "id" | "email"]: Account[K] };
// { id: number; email: string | undefined }  (email is required now)

B a toujours undefined dans le type de email, car Account["email"] l'inclut, mais la propriété elle-même n'est plus optionnelle : { id: 1 } est refusé. C'est pourquoi Partial<T> et ses cousins parcourent keyof T : ils conservent tout ce qu'ils ne changent pas explicitement.

Un type mappé homomorphe générique appliqué à un tableau ou à un tuple produit un tableau ou un tuple, et non un objet à clés numériques. Readonly<string[]> vaut readonly string[], et avec type Box<T> = { [K in keyof T]: { value: T[K] } }, Box<[string, number]> vaut [{ value: string }, { value: number }].

Types mappés et code d'exécution

Comme un type mappé ne fait que décrire un objet, une fonction qui construit un tel objet a besoin de sa propre boucle à l'exécution. Le type décrit alors son résultat :

Les callbacks reçoivent les types de leurs paramètres du type mappé (v est un string dans l'un et un number dans l'autre) sans aucune annotation. Si vous êtes arrivé ici en cherchant la classe Map plutôt que le mappage de types, voir Map en TypeScript.

Questions fréquentes

Qu'est-ce qu'un type mappé en TypeScript ?

Un type qui crée un type objet en parcourant une union de clés : { [K in Keys]: SomeType }. Le plus souvent, les clés sont keyof T, si bien que le nouveau type a les mêmes clés que T avec des types de propriétés transformés, comme dans type Flags<T> = { [K in keyof T]: boolean }.

Que signifient +readonly, -readonly, +? et -? dans un type mappé ?

Ils ajoutent ou retirent des modificateurs. readonly ou +readonly rend chaque propriété readonly, -readonly retire readonly. ? ou +? rend chaque propriété optionnelle, -? les rend obligatoires et retire aussi undefined de leurs types. Required<T> s'écrit avec -?.

Comment renommer les clés dans un type mappé ?

Utilisez une clause as après la clé : { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] } transforme name en getName. Faire correspondre une clé à never dans la clause as la supprime, et c'est ainsi qu'on filtre les propriétés.

Un type mappé est-il la même chose que Map en TypeScript ?

Non. Un type mappé est une transformation de types à la compilation et ne produit aucun code. Map<K, V> est la classe Map de JavaScript, une collection de paires clé-valeur à l'exécution. Les recherches « typescript map type » visent souvent l'un ou l'autre.

Comment sont implémentés Partial et Readonly ?

Comme des types mappés dans la bibliothèque standard : type Partial<T> = { [P in keyof T]?: T[P] } et type Readonly<T> = { readonly [P in keyof T]: T[P] }. Pick et Record sont aussi des types mappés ; Omit est Pick combiné avec Exclude.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER