Kodokon kodokon.com

Mapped types et key remapping : transformer des types

Comprenez comment les mapped types homomorphes préservent les modificateurs et comment la clause as renomme ou filtre les clés.

8 min · 3 questions

Ouvrir cette leçon dans Kodokon

Un mapped type itère sur un ensemble de clés pour produire un nouveau type objet. La forme [K in keyof T] est dite homomorphe : le compilateur sait que la structure vient de T et recopie automatiquement les modificateurs readonly et ? de chaque propriété d'origine. Les préfixes + et - permettent d'ajouter ou de retirer explicitement ces modificateurs : c'est exactement la mécanique interne de Partial, Required et Readonly.

TYPESCRIPT
type Mutable<T> = {
  -readonly [K in keyof T]-?: T[K];
};

interface Config {
  readonly host?: string;
  readonly port?: number;
}

type Draft = Mutable<Config>;
// { host: string; port: number }
Les préfixes - retirent readonly et l'optionalité.

Depuis TypeScript 4.1, la clause as permet le key remapping : chaque clé peut être renommée à la volée, typiquement via un template literal type. Et si la clause produit never, la clé est purement et simplement supprimée du résultat : c'est le mécanisme officiel de filtrage de propriétés. Notez le & string dans l'exemple : keyof T peut contenir des clés symbol ou number, incompatibles avec un template literal.

TYPESCRIPT
type Getters<T> = {
  [K in keyof T & string as `get${Capitalize<K>}`]:
    () => T[K];
};

interface User { name: string; age: number }
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number }

type OnlyMethods<T> = {
  [K in keyof T as
    T[K] extends (...args: never[]) => unknown
      ? K : never]: T[K];
};
Renommage avec as, filtrage en produisant never.

Subtilité de spécification souvent ignorée : appliqué à un tableau ou à un tuple, un mapped type homomorphe ne produit pas un objet avec les clés 0, 1 et length. Le compilateur préserve la nature du type : il transforme les éléments et conserve la forme tableau ou tuple. C'est ce qui permet à Readonly<[number, string]> de rester un tuple. Cette préservation disparaît dès que le mapped type n'est plus homomorphe, par exemple avec [P in K]K est une union arbitraire de clés.

TYPESCRIPT
type Immutable<T> = {
  readonly [K in keyof T]: T[K];
};

type Pair = Immutable<[number, string]>;
// readonly [number, string] : le tuple survit

type List = Immutable<string[]>;
// readonly string[]
Un mapped type homomorphe respecte tableaux et tuples.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Dans un key remapping, que se passe-t-il quand la clause as produit never pour une clé ?
    • La propriété prend le type never
    • La clé est supprimée du type résultant
    • Le compilateur émet une erreur
  2. Que produit { readonly [K in keyof T]: T[K] } appliqué au tuple [number, string] ?
    • Un objet avec les clés 0, 1 et length
    • readonly [number, string]
    • readonly (number | string)[]
    • Une erreur de compilation
  3. Quel est l'effet du modificateur -? dans un mapped type ?
    • Il rend la propriété optionnelle
    • Il retire l'optionalité et le undefined qu'elle induisait
    • Il retire le modificateur readonly
    • Il supprime la propriété du type