Types utilitaires#
Les types utilitaires sont une collection de types génériques prédéfinis par TypeScript, disponibles sans aucun import. Ils permettent de transformer des types existants : rendre des propriétés optionnelles, en sélectionner un sous-ensemble, extraire le type de retour d’une fonction, etc. Construits sur les mécanismes de génériques, de keyof, des types conditionnels et des types mappés, ils constituent la boîte à outils quotidienne de tout développeur TypeScript sérieux.
Transformation de propriétés#
Partial<T>#
Définition 18 (Partial)
Partial<T> construit un nouveau type dans lequel toutes les propriétés de T deviennent optionnelles. C’est l’utilitaire idéal pour les mises à jour partielles d’objets, les formulaires ou les paramètres de configuration avec valeurs par défaut.
interface Utilisateur {
id: number;
nom: string;
email: string;
role: "admin" | "utilisateur";
}
type MiseAJourUtilisateur = Partial<Utilisateur>;
// Équivalent à :
// { id?: number; nom?: string; email?: string; role?: "admin" | "utilisateur" }
function mettreAJour(id: number, modifications: Partial<Utilisateur>): void {
// On peut ne passer que les champs à modifier
console.log(`Mise à jour de l'utilisateur ${id}`, modifications);
}
mettreAJour(1, { nom: "Alice Dupont" }); // ✓ seul nom est modifié
mettreAJour(2, { email: "bob@exemple.fr" }); // ✓ seul email est modifié
Required<T>#
Required<T> est l’inverse de Partial<T> : il rend toutes les propriétés obligatoires, même celles marquées optionnelles dans le type original.
interface OptionsServeur {
hôte?: string;
port?: number;
ssl?: boolean;
}
type ConfigurationComplète = Required<OptionsServeur>;
// { hôte: string; port: number; ssl: boolean }
function démarrer(config: ConfigurationComplète): void {
console.log(`Démarrage sur ${config.hôte}:${config.port}`);
}
Readonly<T>#
Définition 19 (Readonly)
Readonly<T> construit un type dont toutes les propriétés sont marquées readonly : elles ne peuvent plus être réassignées après création de l’objet. Utile pour exprimer l’immuabilité au niveau du type, notamment pour les constantes de configuration ou les données partagées.
interface Point {
x: number;
y: number;
}
const origine: Readonly<Point> = { x: 0, y: 0 };
// origine.x = 1; // Erreur : Cannot assign to 'x' because it is a read-only property
// Très courant avec les tableaux
function somme(valeurs: Readonly<number[]>): number {
return valeurs.reduce((acc, v) => acc + v, 0);
// valeurs.push(4); // Erreur — le tableau est en lecture seule
}
Remarque 14
Readonly<T> est superficiel (shallow) : il rend les propriétés directes de T en lecture seule, mais les objets imbriqués restent mutables. Pour une immutabilité profonde, il faut construire DeepReadonly<T> à la main — ce que nous verrons en section 6. De même, ReadonlyArray<T> (ou readonly T[]) interdit push, pop et les assignations indexées, mais pas la mutation des objets contenus dans le tableau.
Sélection et exclusion#
Pick<T, K>#
Définition 20 (Pick)
Pick<T, K> construit un nouveau type en sélectionnant uniquement les propriétés dont les clés appartiennent à l’union K. K doit être une union de clés valides de T (contrainte : K extends keyof T). C’est l’équivalent d’une projection en algèbre relationnelle.
interface Article {
id: number;
titre: string;
contenu: string;
datePublication: Date;
auteur: string;
vues: number;
}
// Pour un résumé (liste d'articles), on ne veut pas le contenu complet
type RésuméArticle = Pick<Article, "id" | "titre" | "auteur" | "datePublication">;
function listerArticles(): RésuméArticle[] {
return [
{ id: 1, titre: "TypeScript avancé", auteur: "Alice", datePublication: new Date() },
];
}
Omit<T, K>#
Omit<T, K> est le complémentaire de Pick : il construit un type en excluant les propriétés dont les clés appartiennent à K.
// Pour créer un article, on n'a pas encore d'id ni de compteur de vues
type NouvelArticle = Omit<Article, "id" | "vues">;
function créerArticle(données: NouvelArticle): Article {
return { ...données, id: Math.random(), vues: 0 };
}
Record<K, V>#
Définition 21 (Record)
Record<K, V> construit un type objet dont les clés sont du type K (qui doit être assignable à string | number | symbol) et les valeurs du type V. C’est un moyen expressif de typer des dictionnaires ou des tables de correspondance.
type CodePays = "FR" | "DE" | "ES" | "IT";
interface InfoPays {
nom: string;
population: number;
capitale: string;
}
const pays: Record<CodePays, InfoPays> = {
FR: { nom: "France", population: 68_000_000, capitale: "Paris" },
DE: { nom: "Allemagne", population: 84_000_000, capitale: "Berlin" },
ES: { nom: "Espagne", population: 47_000_000, capitale: "Madrid" },
IT: { nom: "Italie", population: 60_000_000, capitale: "Rome" },
};
// Record est pratique pour créer des index ou des caches
type Cache<T> = Record<string, T | undefined>;
const cacheUtilisateurs: Cache<Utilisateur> = {};
Manipulation d’unions#
Exclude<T, U>#
Définition 22 (Exclude)
Exclude<T, U> produit une union en retirant de T tous les membres assignables à U. Il opère sur des unions, pas sur des objets.
type Primitif = string | number | boolean | null | undefined;
type PrimitifNonNull = Exclude<Primitif, null | undefined>;
// string | number | boolean
type SansBoolean = Exclude<string | number | boolean, boolean>;
// string | number
type ÉvénementSouris = "click" | "mousedown" | "mouseup" | "mousemove" | "keydown";
type ÉvénementClavier = "keydown" | "keyup" | "keypress";
type ÉvénementsSourisSeulement = Exclude<ÉvénementSouris, ÉvénementClavier>;
// "click" | "mousedown" | "mouseup" | "mousemove"
Extract<T, U>#
Extract<T, U> est le complément d”Exclude : il conserve uniquement les membres de T qui sont assignables à U.
type Commun = Extract<string | number | boolean, number | boolean>;
// number | boolean
type ÉvénementsCommuns = Extract<ÉvénementSouris, ÉvénementClavier>;
// "keydown"
NonNullable<T>#
Définition 23 (NonNullable)
NonNullable<T> retire null et undefined d’un type union. C’est une application spécialisée d”Exclude : NonNullable<T> est équivalent à Exclude<T, null | undefined>. Il est particulièrement utile après un contrôle de nullité pour affiner le type.
type ValeurOptionnelle = string | number | null | undefined;
type ValeurCertaine = NonNullable<ValeurOptionnelle>;
// string | number
function traiter<T>(valeur: T): NonNullable<T> {
if (valeur == null) throw new Error("Valeur nulle inattendue.");
return valeur as NonNullable<T>;
}
Introspection de fonctions#
TypeScript fournit des utilitaires pour extraire des informations de type à partir des signatures de fonctions et de constructeurs.
Parameters<T> et ReturnType<T>#
Définition 24 (Parameters et ReturnType)
Parameters<T>: extrait les types des paramètres d’une fonctionTsous forme de tuple.ReturnType<T>: extrait le type de retour d’une fonctionT.
Ces deux utilitaires sont implémentés avec infer dans des types conditionnels (voir chapitre 8).
function créerSession(
utilisateurId: number,
durée: number,
options?: { renouveler: boolean }
): { jeton: string; expiration: Date } {
return { jeton: crypto.randomUUID(), expiration: new Date() };
}
type ParamètresCréerSession = Parameters<typeof créerSession>;
// [utilisateurId: number, durée: number, options?: { renouveler: boolean }]
type SessionCrée = ReturnType<typeof créerSession>;
// { jeton: string; expiration: Date }
// Cas d'usage : wrapper qui suit les appels
function avecJournal<T extends (...args: any[]) => any>(
fn: T
): (...args: Parameters<T>) => ReturnType<T> {
return (...args) => {
console.log(`Appel avec`, args);
const résultat = fn(...args);
console.log(`Résultat :`, résultat);
return résultat;
};
}
ConstructorParameters<T> et InstanceType<T>#
class Connexion {
constructor(
readonly hôte: string,
readonly port: number,
readonly ssl: boolean
) {}
connecter(): Promise<void> {
return Promise.resolve();
}
}
type ParamètresConnexion = ConstructorParameters<typeof Connexion>;
// [hôte: string, port: number, ssl: boolean]
type InstanceConnexion = InstanceType<typeof Connexion>;
// Connexion
// Fabrique générique
function fabriquer<T extends new (...args: any[]) => any>(
Constructeur: T,
...args: ConstructorParameters<T>
): InstanceType<T> {
return new Constructeur(...args);
}
const conn = fabriquer(Connexion, "localhost", 5432, false);
// conn : Connexion — type précis inféré
Remarque 15
typeof Connexion et Connexion désignent des choses différentes : Connexion est le type des instances de la classe (équivalent à InstanceType<typeof Connexion>), tandis que typeof Connexion est le type de la classe elle-même (le constructeur). ConstructorParameters et InstanceType attendent typeof Connexion, pas Connexion.
Types de chaînes#
TypeScript fournit quatre utilitaires pour manipuler les types de chaînes littérales (pas les valeurs, mais les types) :
type Salutation = "bonjour" | "bonsoir";
type EnMajuscules = Uppercase<Salutation>; // "BONJOUR" | "BONSOIR"
type EnMinuscules = Lowercase<"ALPHA" | "BETA">; // "alpha" | "beta"
type Capitalisé = Capitalize<Salutation>; // "Bonjour" | "Bonsoir"
type Décapitalisé = Uncapitalize<"Bonjour" | "Bonsoir">; // "bonjour" | "bonsoir"
Ces utilitaires sont particulièrement utiles pour dériver des noms de propriétés de façon programmatique :
type GéteursPour<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Config {
hôte: string;
port: number;
}
type GettersConfig = GéteursPour<Config>;
// { getHôte: () => string; getPort: () => number }
Exemple 6 (Construction d’une API fluide)
type Événements = "click" | "focus" | "blur";
// Générer les noms de gestionnaires : onClick, onFocus, onBlur
type Gestionnaires = {
[K in Événements as `on${Capitalize<K>}`]: (event: Event) => void;
};
// { onClick: (event: Event) => void;
// onFocus: (event: Event) => void;
// onBlur: (event: Event) => void }
Combinaisons pratiques#
L’intérêt des types utilitaires se révèle pleinement lorsqu’on les compose ou qu’on les étend pour répondre à des besoins spécifiques.
DeepPartial<T>#
Partial<T> n’est pas récursif : les propriétés de premier niveau deviennent optionnelles, mais les objets imbriqués restent inchangés. On peut construire une version récursive :
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
interface Adresse {
rue: string;
ville: string;
pays: string;
}
interface Profil {
nom: string;
âge: number;
adresse: Adresse;
}
type ProfilPartiel = DeepPartial<Profil>;
// { nom?: string; âge?: number; adresse?: { rue?: string; ville?: string; pays?: string } }
function mettreAJourProfil(id: string, modifications: DeepPartial<Profil>): void {
// On peut ne mettre à jour que la ville sans fournir rue et pays
}
mettreAJourProfil("42", { adresse: { ville: "Lyon" } }); // ✓
Mutable<T>#
Readonly<T> n’a pas d’équivalent officiel pour retirer readonly. On peut le construire en utilisant le modificateur -readonly des types mappés :
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
interface ConfigImmuable {
readonly hôte: string;
readonly port: number;
}
type ConfigModifiable = Mutable<ConfigImmuable>;
// { hôte: string; port: number } — readonly retiré
// De même, on peut retirer l'optionnalité :
type Obligatoire<T> = {
[K in keyof T]-?: T[K];
};
Remarque 16
Les modificateurs de type mappé -readonly et -? permettent respectivement de retirer readonly et l’optionnalité (?) sur chaque propriété. Leur pendant positif (readonly et ?) est celui qu’utilisent Readonly<T> et Partial<T>. Cette symétrie rend le système de types très expressif pour transformer des types en profondeur.
Composition#
On peut enchaîner les types utilitaires pour exprimer des transformations complexes de façon lisible :
// Un formulaire de mise à jour : toutes les propriétés sauf id sont optionnelles et non-null
type FormulaireEdition<T extends { id: unknown }> = Partial<Omit<T, "id">> & Pick<T, "id">;
// Exemple : éditer un Utilisateur
type FormulaireUtilisateur = FormulaireEdition<Utilisateur>;
// { id: number } & { nom?: string; email?: string; role?: "admin" | "utilisateur" }
// Lire en lecture seule uniquement certaines propriétés
type ArticleSûr = Readonly<Pick<Article, "id" | "titre">> & Omit<Article, "id" | "titre">;
Visualisation : grille des types utilitaires#
Résumé#
Ce chapitre a passé en revue l’ensemble des types utilitaires fournis par TypeScript :
Transformation de propriétés :
Partial<T>rend toutes les propriétés optionnelles,Required<T>les rend obligatoires,Readonly<T>les rend non-réassignables. Ces transformations sont superficielles (premier niveau seulement).Sélection et exclusion :
Pick<T, K>extrait un sous-ensemble de propriétés,Omit<T, K>en exclut certaines,Record<K, V>construit un dictionnaire typé.Manipulation d’unions :
Exclude<T, U>retire des membres d’une union,Extract<T, U>n’en conserve que certains,NonNullable<T>éliminenulletundefined.Introspection de fonctions :
Parameters<T>etReturnType<T>extraient les types de paramètres et de retour ;ConstructorParameters<T>etInstanceType<T>font de même pour les classes.Types de chaînes :
Uppercase,Lowercase,Capitalize,Uncapitalizetransforment des types de chaînes littérales, idéaux pour générer des noms de propriétés programmatiquement.Compositions :
DeepPartial<T>,Mutable<T>et d’autres types construits à la main démontrent la puissance de la composition. Les modificateurs-readonlyet-?dans les types mappés permettent l’inverse deReadonlyetPartial.
Dans le chapitre suivant, nous explorerons le mécanisme sous-jacent qui rend possible nombre de ces types utilitaires : les types conditionnels, avec leur syntaxe T extends U ? X : Y, la distribution sur les unions, et le mot-clé infer pour l’extraction de sous-types.