3e étageTS TypeScriptPièce 5/7Intermédiaire

Génériques et types utilitaires

Écris des fonctions et des types réutilisables avec les génériques, puis dérive des types avec Partial, Pick, Omit et Record.

Ce que tu vas apprendre

  • Écrire une fonction et un type génériques.
  • Contraindre un générique avec extends.
  • Dériver de nouveaux types avec les types utilitaires.

Le besoin : une fonction pour tous les types

Imagine une fonction qui renvoie le premier élément d'une liste. Pour des textes, tu écris string[]. Pour des nombres, il faudrait une seconde fonction.

Sans générique, tu es tenté par any :

function premier(liste: any[]): any {
  return liste[0]
}

const x = premier([1, 2, 3]) // any : on a perdu le type

Le résultat n'est plus vérifié. Un générique garde le lien entre l'entrée et la sortie.

Un générique, c'est un type en paramètre

Un générique est un type que tu laisses en paramètre, comme tu laisses une valeur en paramètre d'une fonction. On le note entre chevrons.

function premier<T>(liste: T[]): T {
  return liste[0]
}

const a = premier(["ail", "sel"]) // string
const b = premier([1, 2, 3])      // number

T est un nom d'usage pour « un type à préciser ». TypeScript le déduit à chaque appel d'après les arguments.

Tu peux aussi le donner toi-même : premier<string>(["ail", "sel"]). En général, l'inférence suffit.

Contraindre un générique

Parfois le type doit avoir une certaine forme. Le mot extends pose une condition.

function nomsDe<T extends { nom: string }>(elements: T[]): string[] {
  return elements.map((element) => element.nom)
}

nomsDe([{ nom: "Crêpes", duree: 20 }]) // accepté
nomsDe([{ duree: 20 }])                // Erreur

T peut être n'importe quel type, à condition d'avoir une propriété nom de type texte. Dans la fonction, tu peux donc lire element.nom sans risque.

Un type générique

Les interfaces et les alias acceptent aussi des paramètres de type.

interface Reponse<T> {
  donnees: T
  erreur: string | null
}

type ReponseRecettes = Reponse<Recette[]>

Une même structure sert à toutes tes réponses d'API : tu précises seulement le contenu. Tu as déjà rencontré cette notation avec Array<string>, équivalent de string[].

Les types utilitaires

TypeScript fournit des types utilitaires : des outils qui transforment un type existant en un autre. Ils évitent de recopier des interfaces entières.

Voici l'interface de départ :

interface Recette {
  id: number
  nom: string
  duree: number
  vegetarien: boolean
}

Partial : tout devient optionnel

Partial<Recette> rend toutes les propriétés facultatives. C'est pratique pour une mise à jour partielle.

function modifier(recette: Recette, changements: Partial<Recette>) {
  return { ...recette, ...changements }
}

modifier(gratin, { duree: 50 })

Tu n'envoies que les champs qui changent. Required<Recette> fait l'inverse, Readonly<Recette> interdit les modifications.

Pick et Omit : garder ou retirer

Pick conserve les propriétés listées. Omit retire celles que tu indiques.

type Apercu = Pick<Recette, "nom" | "duree">
type NouvelleRecette = Omit<Recette, "id">

Apercu n'a que nom et duree. NouvelleRecette possède tout sauf id, ce qui convient à un formulaire de création, car l'identifiant est attribué après coup.

Les clés sont écrites comme des types littéraux, séparés par |. Une faute de frappe dans un nom est signalée.

Record : un dictionnaire

Record<Cles, Valeur> décrit un objet dont les clés et les valeurs ont les types donnés.

type Difficulte = "facile" | "moyen" | "difficile"

const couleurs: Record<Difficulte, string> = {
  facile: "vert",
  moyen: "orange",
  difficile: "rouge"
}

Si tu oublies une des trois clés, TypeScript le signale. Record<string, number> convient à un objet dont les clés ne sont pas connues d'avance.

Récapitulatif des utilitaires

TypeEffet
Partial<T>Toutes les propriétés deviennent facultatives
Required<T>Toutes les propriétés deviennent obligatoires
Readonly<T>Toutes les propriétés passent en lecture seule
Pick<T, K>Garde seulement les clés K
Omit<T, K>Retire les clés K
Record<K, V>Crée un objet de clés K et de valeurs V
Dérive tes types plutôt que de les copier. Si Recette change, Apercu et NouvelleRecette suivent sans que tu y touches.

À retenir

  • Un générique <T> est un type passé en paramètre, déduit à l'appel.
  • Il garde le lien entre l'entrée et la sortie, là où any le perd.
  • T extends { nom: string } impose une forme minimale.
  • Partial, Required et Readonly modifient toutes les propriétés d'un type.
  • Pick garde des clés, Omit en retire, Record fabrique un dictionnaire.
  • Dériver un type évite de dupliquer ses propriétés.

Pour aller plus loin

Sources officielles

Ensuite

Projet
Lignes de code
Niveau
3e étage
Appartement
TypeScript
Pièce
5/7
Plan n°
ts/05.generiques-et-utilitaires
Lignes de code · fiches rédigées à partir des documentations officielles, sources citées en bas de chaque page.