Les génériques sont des paramètres de type. Une fonction générique déclare un type d'emplacement, généralement appelé T, et chaque appel le remplit : les types qui entrent décident des types qui sortent. Une même fonction marche alors pour les nombres, les chaînes et vos propres objets, et chaque appel reste entièrement typé.
<T> après le nom de la fonction déclare le paramètre de type. items: T[] l'utilise pour l'entrée et T | undefined pour la sortie, ce qui relie les deux. Vous n'avez jamais écrit number ni string : TypeScript a inféré T à partir de l'argument.
Pourquoi pas any ou unknown ?
Sans génériques, vous typeriez le paramètre en any ou en unknown. Les deux acceptent tous les tableaux, et les deux perdent le lien entre l'entrée et la sortie :
| Type du paramètre | Accepte toute entrée | Type du résultat | Vérifié |
|---|---|---|---|
any | oui | any | non : les erreurs compilent |
unknown | oui | unknown | oui, mais vous devez affiner avant de l'utiliser |
T (générique) | oui | lié à l'entrée | oui, avec le type précis |
L'inférence des arguments de type
En général, TypeScript infère les arguments de type à partir des valeurs passées. Vous pouvez aussi les écrire explicitement entre chevrons à l'appel, ce qui est nécessaire quand rien dans les arguments ne mentionne T, ou quand l'inférence choisit quelque chose que vous ne vouliez pas.
Deux résultats d'inférence méritent d'être connus. pair(1, "one") sans argument explicite est une erreur : TypeScript infère T = number à partir du premier argument, puis signale Argument of type 'string' is not assignable to parameter of type 'number'. (TS2345). Et emptyList() sans aucun argument reçoit T = unknown, ce qui donne unknown[], rarement ce que vous voulez.
Plusieurs paramètres de type
Une fonction peut déclarer autant de paramètres de type que nécessaire, séparés par des virgules. Chacun est inféré indépendamment.
mapValues infère trois choses à la fois : K vaut "tea" | "cake", V vaut number, et R vaut string d'après le retour du callback. K extends string est une contrainte, présentée à la page suivante.
Interfaces et alias de type génériques
Les types peuvent aussi prendre des paramètres. Vous écrivez alors l'argument quand vous utilisez le type : Box<number>, ApiResponse<User>. La plupart des types de collections intégrés fonctionnent ainsi : Array<T>, Map<K, V>, Promise<T>, Record<K, V>.
Result<T, E = string> montre aussi un paramètre de type par défaut : Result<number> signifie Result<number, string>. Comme les paramètres de fonction optionnels, les paramètres avec valeur par défaut doivent venir après les obligatoires.
Classes génériques
Une classe prend ses paramètres de type après son nom, et chaque instance les fixe. Les champs, les méthodes et les paramètres du constructeur peuvent tous les utiliser.
Si le constructeur prend un T, vous pouvez omettre l'argument explicite : new Box(5) infère Box<number>. Les membres statiques appartiennent à la classe elle-même, pas à une instance : ils ne peuvent donc pas utiliser le paramètre de type de la classe (Static members cannot reference class type parameters., TS2302).
Fonctions fléchées génériques et types de fonctions
La liste des paramètres de type se place avant la liste des paramètres. La même syntaxe décrit un type de fonction générique.
const last = <T>(items: T[]): T | undefined => items[items.length - 1];
type Mapper = <T, R>(items: T[], fn: (item: T) => R) => R[];
const mapAll: Mapper = (items, fn) => items.map(fn);
// In a .tsx file, <T> looks like a JSX tag. Add a trailing comma:
const lastTsx = <T,>(items: T[]) => items[items.length - 1];
Inférer des types littéraux avec les paramètres de type const
Par défaut, un générique infère des types élargis : pair("a", "b") donne [string, string]. Quand les valeurs exactes comptent (noms de routes, noms d'événements, listes de colonnes), marquez le paramètre const (TypeScript 5.0 et ultérieurs) et il infère comme si l'argument était écrit as const :
L'appelant écrit un tableau ordinaire, et la fonction garde les types littéraux.
Quand ne pas utiliser les génériques
Un paramètre de type se justifie quand il relie deux choses : un paramètre et le type de retour, deux paramètres, ou un paramètre et un callback. Si T n'apparaît qu'une fois, il n'apporte rien et un type simple est plus clair.
// Pointless: T is used once, so it is just a longer way to write unknown
function logValue<T>(value: T): void {
console.log(value);
}
// Clearer
function logValueSimple(value: unknown): void {
console.log(value);
}
// Also pointless: returns T but nothing connects T to an argument,
// so the caller is really just asserting a type
function parseJson<T>(text: string): T {
return JSON.parse(text);
}
Le dernier cas est courant et trompeur : parseJson<User>(text) a l'air sûr au niveau des types mais ne vérifie rien, exactement comme JSON.parse(text) as User. Renvoyez plutôt unknown et validez. Autres signes d'abus : un paramètre de type qui reçoit toujours le même argument, ou qu'on pourrait remplacer par une union de deux types connus.
Pour les génériques qui ont besoin de savoir quelque chose sur T (qu'il a une length, ou que K est une clé de T), les contraintes avec extends sont l'étape suivante.
Questions fréquentes
Que sont les génériques en TypeScript ?
Les génériques sont des paramètres de type : des emplacements comme T remplis par un vrai type à chaque utilisation d'une fonction, d'une interface, d'un alias de type ou d'une classe. function first<T>(items: T[]): T | undefined fonctionne pour n'importe quel tableau, et le résultat a le type des éléments du tableau passé : first([1, 2]) vaut number | undefined et first(["a"]) vaut string | undefined.
Quelle est la différence entre les génériques et any ?
any désactive la vérification des types : la valeur qui entre et celle qui sort n'ont aucun lien, et le résultat est any lui aussi. Un générique conserve le lien : identity<T>(x: T): T renvoie exactement le type passé. Utilisez unknown si vous acceptez n'importe quoi sans avoir besoin de rendre le type, et un générique si vous en avez besoin.
Que signifie <T> en TypeScript ?
Il déclare un paramètre de type nommé T. Dans function wrap<T>(value: T), T est une variable de type que TypeScript remplit à partir de l'argument à chaque appel, ou que vous passez explicitement, comme wrap<string>("a"). Le nom T n'est qu'une convention ; des noms plus longs comme TItem ou Key fonctionnent de la même façon.
Comment écrire une fonction fléchée générique en TypeScript ?
Placez la liste des paramètres de type avant les paramètres : const first = <T>(items: T[]): T | undefined => items[0];. Dans un fichier .tsx, <T> serait lu comme une balise JSX : écrivez donc <T,> avec une virgule finale, ou <T extends unknown>.
Comment définir un type par défaut pour un générique en TypeScript ?
Ajoutez = Type après le paramètre : interface ApiResponse<T = unknown> { data: T }. ApiResponse sans argument signifie alors ApiResponse<unknown>. Les paramètres avec valeur par défaut doivent venir après ceux qui n'en ont pas, comme les paramètres de fonction optionnels.