Generics
Les génériques sont l'une des fonctionnalités les plus puissantes de TypeScript. Ils nous permettent de créer des fonctions, des classes ou des interfaces réutilisables qui peuvent fonctionner avec une variété de types tout en restant type-safe.
Consistence du type
Imaginons une fonction simple qui retourne ce qu'on lui passe en argument. Sans générique, on pourrait être tenté d'utiliser le type any, mais on perdrait alors tout le typage de TypeScript.
function identity(arg: any): any {
return arg;
}
let output = identity("monString");Le type générique
Un générique fonctionne comme un paramètre d'une fonction mais pour un type. On utilise une lettre entre chevrons, traditionnellement T (pour Type), comme un espace réservé.
function identity<T>(arg: T): T {
return arg;
}
// Magie de l'inférence ! 🪄
// Pas besoin d'écrire <string>. TypeScript voit l'argument "monString"
// et DÉDUIT que T doit être de type 'string'.
const output = identity("monString"); // 'output' est bien de type 'string'.
// Pareil ici, TypeScript infère le type 'number'.
const numOutput = identity(123); Cas concret
Lorsque Typescript ne peut pas deviner le type d'un objet, nous devons prendre la main et lui indiquer de quoi il va s'agir.
C'est nottament le cas avec un appel HTTP par exemple.
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Erreur HTTP: ${response.status}`);
}
return response.json();
}Quand on appelle cette fonction, le seul argument est une URL (string).
TypeScript n'a aucun moyen de savoir quelle sera la forme des données JSON retournées.
Dans ce cas là on peut lui expliciter le type.
interface User {
id: number;
name: string;
}
// On dit à TypeScript : "Fais-moi confiance, le JSON de cette URL
// correspondra à un tableau d'objets User".
const users = await fetchData<User[]>('https://api.github.com/users');
// 'users' est maintenant parfaitement typé en tant que User[].
// On a accès à users[0].name avec autocomplétion et en toute sécurité.
users[0].name;type et interface
Les génériques ne sont pas limités aux fonctions. On peut créer des types et des interfaces réutilisables, ce qui est très courant pour standardiser les réponses d'API.
// Une structure de réponse d'API générique
interface ApiResponse<DataType> {
data: DataType;
status: 'success' | 'error';
timestamp: Date;
}
const usersResponse: ApiResponse<User[]> = {
data: [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }],
status: 'success',
timestamp: new Date()
};
const errorResponse: ApiResponse<null> = {
data: null,
status: 'error',
timestamp: new Date()
};Utility types
TypeScript fournit une série de types génériques utilitaires (Utility Types) conçus pour vous aider à manipuler et transformer vos types existants sans avoir à réécrire des logiques complexes. Ils agissent comme des fonctions, mais pour vos types.
Parmi les plus courants, vous trouverez :
Partial<T>: Rend toutes les propriétés du type T optionnelles. Idéal pour les objets de mise à jour.Pick<T, K>: Crée un nouveau type en ne "piochant" (picking) que les propriétés K du type T.Omit<T, K>: Fait l'inverse, il crée un type en omettant les propriétés K du type T.Required<T>: Rend toutes les propriétés, même celles qui étaient optionnelles, obligatoires.NonNullable<T>: Construit un type en excluant null et undefined.
interface User {
id: number;
name: string;
email?: string;
age: number | null;
}
// Un type avec uniquement l'id et le nom
type UserPreview = Pick<User, 'id' | 'name'>;
// Un type User sans l'id
type NewUser = Omit<User, 'id'>;
// Le type de 'age' sans la possibilité d'être 'null'
type Age = NonNullable<User['age']>;
// Le type de 'email' sans la possibilité d'être 'undefined'
type Email = NonNullable<User['email']>;Il en existe beaucoup d'autres pour des cas d'usage plus spécifiques. Pour une exploration complète, le mieux est de consulter directement la documentation officielle.