Vol. 6 · № 01 · Un atelier TypeScriptA TypeScript workshop

Mapped types & keyof, ou calculer plutôt que recopier Mapped types & keyof, or compute rather than copy

Une variante d'un type réécrite à la main diverge de sa source : ajoute un champ, la copie ment. Le mapped type la calcule — [K in keyof T] parcourt les clés et les réécrit. Partial, Readonly, Record ne sont pas magiques : ce sont des boucles d'une ligne. Le prix, qui montera : dériver par confort couple ce qui ne devrait pas l'être. A type's variant rewritten by hand drifts from its source: add a field, the copy lies. The mapped type computes it — [K in keyof T] walks the keys and rewrites them. Partial, Readonly, Record aren't magic: they're one-line loops. The price, which will rise: deriving for convenience couples what shouldn't be.

AudienceAudience
Dev à l'aise avec les génériques contraints, prêt à transformer un type en un autre Dev comfortable with constrained generics, ready to transform one type into another
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●●●○ Mapped typeskeyofTradeoff
SommaireContents ·
№ 01CadrageFraming3 min

Un type dérivé, réécrit à la main, diverge de sa source. Calculé, jamais.A derived type, hand-rewritten, drifts from its source. Computed, never.

On a souvent besoin d'une variante d'un type : la même forme, mais tous les champs optionnels, ou en lecture seule, ou un sous-ensemble. Le réflexe est de la réécrire à la main — et de créer une seconde description que rien n'oblige à suivre la première. Le jour où la source gagne un champ, la copie l'ignore. TypeScript offre l'autre voie : calculer ce type à partir de l'original, pour qu'ils ne divergent jamais. C'est tout le volume — le langage dans le langage.You often need a variant of a type: the same shape, but all fields optional, or read-only, or a subset. The reflex is to rewrite it by hand — and to create a second description nothing forces to track the first. The day the source gains a field, the copy ignores it. TypeScript offers the other path: compute that type from the original, so they never diverge. That's the whole volume — the language within the language.

Réécrit à la main — deux vérités qui dériventHand-rewritten — two truths that drift
type User = { id: string; name: string; email: string };

// réécrit à la main — deux descriptions de la même chose à maintenir :
type UserUpdate = { id?: string; name?: string; email?: string };
// Ajoute 'role' à User → UserUpdate l'ignore en silence. Les deux divergent.
Que devient la variante quand la source change ?What happens to the variant when the source changes?
Réécrit à la mainHand-rewritten
dérive en silencedrifts silently
un champ ajouté, et la copie mentadd a field, and the copy lies
Calculé (mapped type)Computed (mapped type)
suit la sourcefollows the source
recalculé à chaque changementrecomputed on every change

Deux descriptions de la même forme finissent toujours par diverger. Calculer la variante depuis la source supprime la seconde description : il n'y a plus qu'une vérité, et la variante en découle.Two descriptions of the same shape always end up diverging. Computing the variant from the source removes the second description: there's only one truth left, and the variant follows from it.

Calculé — une seule source de véritéComputed — a single source of truth
type User = { id: string; name: string; email: string };

type UserUpdate = Partial<User>;   // CALCULÉ depuis User — ne dérive jamais
// Ajoute 'role' à User → UserUpdate gagne 'role?' automatiquement.
Le réflexe du numéroThe issue's reflex

« Ce type, je le calcule à partir d'un autre, ou je le réécris (et je le désynchronise) à la main ? » Dès qu'une forme est une transformation d'une autre, dérive-la — un mapped type garde les deux liées pour toujours."This type — do I compute it from another, or rewrite it (and desync it) by hand?" The moment a shape is a transformation of another, derive it — a mapped type keeps the two linked forever.

Pourquoi ce numéro ouvre le Vol 6.Why this issue opens Vol 6.

Jusqu'ici, les types décrivaient et reliaient des valeurs. Le Vol 6 franchit un seuil : transformer un type pour en produire un autre. Le mapped type en est le premier verbe — parcourir les clés d'une forme et les réécrire. Tout le reste du volume (conditionnels, template literals) prolonge cette idée, avec sa puissance et son prix.Until now, types described and related values. Vol 6 crosses a threshold: transforming a type to produce another. The mapped type is its first verb — walk a shape's keys and rewrite them. The rest of the volume (conditionals, template literals) extends this idea, with its power and its price.

№ 02La surpriseThe surprise4 min

Partial, Readonly, Record ne sont pas magiques — ce sont des boucles sur les clés.Partial, Readonly, Record aren't magic — they're loops over keys.

On utilise les utility types comme des primitives offertes par le langage. Surprise en soulevant le capot : Partial<T> est littéralement { [K in keyof T]?: T[K] }. Trois symboles. Aucune magie, aucun traitement spécial du compilateur : juste une itération sur les clés de T, qui réécrit chaque propriété. Readonly et Required : bâtis sur le même [K in keyof T]. Pick et Record bouclent sur des clés qu'on leur donne ([P in K]), et Omit n'est qu'un Pick des clés restantes. Comprendre la boucle, c'est pouvoir écrire les siens.People use utility types as language-given primitives. Surprise, lifting the hood: Partial<T> is literally { [K in keyof T]?: T[K] }. Three symbols. No magic, no special compiler handling: just an iteration over T's keys, rewriting each property. Readonly and Required: built on the same [K in keyof T]. Pick and Record loop over keys you hand them ([P in K]), and Omit is just a Pick of the remaining keys. Understand the loop, and you can write your own.

L'usage — une transformation nomméeThe use — a named transformation
type UserUpdate = Partial<User>;
//   ≡ { id?: string; name?: string; email?: string }

type Frozen = Readonly<User>;
//   ≡ { readonly id: string; readonly name: string; readonly email: string }

Partial rend tout optionnel, Readonly tout immuable. On les lit comme des mots-clés — pratiques, opaques. Mais ce ne sont pas des mots-clés : ce sont des types ordinaires, définis dans lib.d.ts, qu'on pourrait réécrire soi-même.Partial makes everything optional, Readonly everything immutable. You read them as keywords — handy, opaque. But they aren't keywords: they're ordinary types, defined in lib.d.ts, that you could rewrite yourself.

Les utility types ne sont pas des cadeaux du compilateur — ce sont des boucles que tu sais maintenant lire, et donc écrire. [K in keyof T] est le for des types.Utility types aren't gifts from the compiler — they're loops you can now read, and therefore write. [K in keyof T] is the for of types.
№ 03La mécaniqueThe mechanics5 min

Itérer sur keyof T, lire T[K], poser ou retirer un modificateur.Iterate over keyof T, read T[K], add or strip a modifier.

Quatre pièces, déjà toutes croisées. keyof T (Vol 5.2) est l'union des clés. [K in keyof T] itère K sur cette union. T[K] est le type de la valeur à la clé K. Et les modificateurs ? et readonly s'ajoutent — ou se retirent, avec le préfixe -. On peut aussi transformer la valeur elle-même (T[K] | null). Avec ces briques, on écrit n'importe quelle transformation de forme — y compris partir d'une union de clés nue, comme le fait Record.Four pieces, all already met. keyof T (Vol 5.2) is the key union. [K in keyof T] iterates K over that union. T[K] is the value type at key K. And the ? and readonly modifiers add — or strip, with the - prefix. You can also transform the value itself (T[K] | null). With these bricks, you write any shape transformation — including starting from a bare key union, as Record does.

Bâtir les siens — transformer la valeur, retirer un modificateurBuild your own — transform the value, strip a modifier
type Nullable<T> = { [K in keyof T]: T[K] | null };   // transforme la VALEUR
type Mutable<T>  = { -readonly [K in keyof T]: T[K] };  // RETIRE readonly (-)
type Required<T> = { [K in keyof T]-?: T[K] };          // RETIRE l'optionnel (-?)
Record — mapper une union de clés vers une valeurRecord — map a key union to a value
// Record part de zéro : il MAPPE une union de clés vers une valeur.
type Record<K extends keyof any, V> = { [P in K]: V };

type Flags = Record<"admin" | "user", boolean>;
//   ≡ { admin: boolean; user: boolean }
Les quatre piècesThe four pieces
Parcourir & lireWalk & read
keyof Tl'union des clésthe key union
[K in …]itère K sur l'unioniterates K over the union
T[K]le type à la clé Kthe type at key K
ModifierModify
?rend optionnelmakes optional
readonlyrend immuablemakes immutable
-? / -readonlyretire le modificateurstrips the modifier
Les voisins, seulement nommés.The neighbors, only named.

Pick et Omit sont les mêmes boucles sur un sous-ensemble de clés. Le key remapping ([K in keyof T as …]) permet de renommer les clés — souvent avec les template literal types du 6.3. Les mapped types « homomorphes » (sur keyof T directement) préservent les modificateurs d'origine ; keyof any vaut string | number | symbol.Pick and Omit are the same loops over a subset of keys. Key remapping ([K in keyof T as …]) lets you rename keys — often with the template literal types of 6.3. "Homomorphic" mapped types (over keyof T directly) preserve the original modifiers; keyof any is string | number | symbol.

№ 04Le piègeThe trap4 min

Dériver par confort d'une source trop large couple ce qui devrait rester séparé.Deriving for convenience from a too-wide source couples what should stay apart.

Le mapped type n'a de valeur que si la source est vraiment la vérité de la variante. Le piège : dériver d'une source trop large parce que c'est sous la main. Partial<User> pour un formulaire d'édition a l'air parfait — jusqu'à ce qu'on réalise qu'il rend éditables l'id et le createdAt posés par le serveur. La dérivation a propagé des champs qu'on ne voulait jamais offrir à l'édition. Calculer, oui — mais à partir de la bonne source, celle qui exprime l'intention.A mapped type is only worth it if the source is genuinely the truth of the variant. The trap: deriving from a too-wide source because it's at hand. Partial<User> for an edit form looks perfect — until you realize it makes the server-set id and createdAt editable. The derivation propagated fields you never meant to offer for editing. Compute, yes — but from the right source, the one that expresses intent.

La source entière entraîne des champs non voulusThe whole source drags in unwanted fields
type User = {
  id: string; createdAt: Date;     // ← posés par le serveur, JAMAIS éditables
  name: string; email: string;
};

type EditableUser = Partial<User>;   // ✗ dérive par confort de la source ENTIÈRE
// Le formulaire laisse maintenant éditer id et createdAt. Un champ serveur
// ajouté à User devient éditable, en silence. La dérivation a couplé trop large.

La dérivation fonctionne trop bien : elle suit User fidèlement, y compris ses champs serveur. Le lien « pas de dérive » est réel, mais il couple des choses qui ne devraient pas bouger ensemble — l'édition et la persistance. Le contre-exemple : dériver parce que c'est commode, pas parce que la source est la bonne.The derivation works too well: it tracks User faithfully, including its server fields. The "no drift" link is real, but it couples things that shouldn't move together — editing and persistence. The counter-example: deriving because it's convenient, not because the source is the right one.

Le prix, qui montera au fil du volume.The price, rising through the volume.

Un mapped type n'achète une vraie garantie — l'absence de dérive — que si la source doit bouger avec la variante. Sinon, c'est de la cérémonie qui couple les mauvaises choses, et un type écrit à la main est plus clair. Test : « ces deux types doivent-ils changer ensemble, toujours ? » Si oui, dérive ; si non, écris-le. Le danger du type trop malin — illisible, lent à typer — ne fait que commencer : il culmine au 6.2 et au 6.3, avec les conditionnels.A mapped type only buys a real guarantee — no drift — if the source must move with the variant. Otherwise it's ceremony that couples the wrong things, and a hand-written type is clearer. Test: "must these two types always change together?" If yes, derive; if no, write it. The danger of the too-clever type — unreadable, slow to typecheck — is only starting: it peaks in 6.2 and 6.3, with conditionals.

Calculer un type ne vaut que si la source est sa vérité. Dériver par commodité, c'est coupler ce que le hasard a mis côte à côte — pas ce qui doit bouger ensemble.Computing a type is only worth it if the source is its truth. Deriving for convenience couples what chance placed side by side — not what must move together.
№ 05Bilan & éditoWrap-up & editorial2 min

Calculer une forme à partir d'une autre — pour qu'elles ne divergent jamais.Compute one shape from another — so they never diverge.

Le mapped type est le premier verbe du langage des types : [K in keyof T] parcourt les clés d'une forme et les réécrit. Partial, Readonly, Record, Pick ne sont que ça — des boucles d'une ligne. La puissance : une variante calculée qui ne dérive jamais de sa source. Le prix, qui montera : la cérémonie qui couple les mauvaises choses, ou le type trop malin que plus personne ne lit.The mapped type is the first verb of the type language: [K in keyof T] walks a shape's keys and rewrites them. Partial, Readonly, Record, Pick are just that — one-line loops. The power: a computed variant that never drifts from its source. The price, which will rise: ceremony that couples the wrong things, or the too-clever type no one reads anymore.

Tu veux…You want to…Le gesteThe moveCe que ça coûteWhat it costs
Dériver une variante d'un typeDerive a variant of a typeUn mapped type [K in keyof T] (ou Partial/Pick…).A mapped type [K in keyof T] (or Partial/Pick…).Se recalcule à chaque changement de la source — le but.Recomputes on every source change — the goal.
Transformer chaque valeurTransform each value[K in keyof T]: F<T[K]>.[K in keyof T]: F<T[K]>.Il faut que la source soit la vérité de la variante.The source must be the variant's truth.
Ajouter/retirer un modificateurAdd/strip a modifier?, readonly, -?, -readonly.?, readonly, -?, -readonly.Savoir lire les modificateurs et leur retrait.Knowing how to read modifiers and their removal.
Ne PAS l'employerNOT use itDériver par confort d'une source trop large.Derive for convenience from a too-wide source.Couple des champs qui ne devraient pas l'être.Couples fields that shouldn't be coupled.
Deux descriptions de la même forme finissent par diverger. Une seule, et l'autre calculée, ne le peuvent pas — à condition que la source soit vraiment la vérité.Two descriptions of the same shape end up diverging. One, and the other computed, can't — provided the source is genuinely the truth.
Mot de l'éditeurFrom the editor

« Calculer, plutôt que recopier. »"Compute, rather than copy."

TypeScript cache un langage dans le langage : on n'y manipule pas des valeurs mais des types, et on peut les transformer. Le mapped type en est le premier geste, et le plus utile au quotidien — parcourir les clés d'une forme pour en produire une autre. Une fois qu'on a vu que Partial<T> n'est que { [K in keyof T]?: T[K] }, toute la famille des utility types cesse d'être magique : ce sont des boucles, lisibles et imitables. Le gain est réel et précis : un type dérivé qui suit sa source pour toujours, là où une copie à la main aurait fini par mentir. C'est le même réflexe que le discriminant, le brand, le schéma — une seule source de vérité, le reste en découle.TypeScript hides a language within the language: there you manipulate not values but types, and you can transform them. The mapped type is its first gesture, and the most useful day to day — walk a shape's keys to produce another. Once you've seen that Partial<T> is just { [K in keyof T]?: T[K] }, the whole utility-type family stops being magic: they're loops, readable and imitable. The gain is real and precise: a derived type that follows its source forever, where a hand-copy would have ended up lying. It's the same reflex as the discriminant, the brand, the schema — one source of truth, the rest follows.

Mais le volume porte aussi un avertissement, et il commence ici. Calculer ne vaut que si la source est la vérité de ce qu'on calcule ; dériver par commodité couple des choses que rien ne destinait à bouger ensemble. Le mapped type transforme chaque clé de la même façon — uniformément. Pour brancher — produire un type ou un autre selon une condition — il faut un nouvel outil : les types conditionnels, T extends U ? X : Y, et leur compagnon infer qui extrait un type capturé. Le extends du Vol 5 y revient, non plus comme une borne, mais comme un test. Et avec eux monte d'un cran le danger du type trop malin.But the volume also carries a warning, and it starts here. Computing is only worth it if the source is the truth of what you compute; deriving for convenience couples things nothing meant to move together. The mapped type transforms every key the same way — uniformly. To branch — produce one type or another depending on a condition — you need a new tool: conditional types, T extends U ? X : Y, and their companion infer that extracts a captured type. Vol 5's extends returns there, no longer as a bound, but as a test. And with them, the danger of the too-clever type climbs a notch.