Advanced Typescript
Typescript

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 (: any): any {
  return ;
}

let output = ("monString");
let output: any

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 <>(: ):  {
  return ;
}

// 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  = ("monString"); // 'output' est bien de type 'string'.

// Pareil ici, TypeScript infère le type 'number'.
const  = (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 <>(: string): <> {
  const  = await ();
  if (!.) {
    throw new (`Erreur HTTP: ${.}`);
  }
  return .();
}

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 {
  : number;
  : string;
}

// On dit à TypeScript : "Fais-moi confiance, le JSON de cette URL
// correspondra à un tableau d'objets User".
const  = await <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é.
[0].;

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 <> {
  : ;
  : 'success' | 'error';
  : Date;
}

const : <User[]> = {
  : [{ : 1, : 'Alice' }, { : 2, : 'Bob' }],
  : 'success',
  : new ()
};

const : <null> = {
  : null,
  : 'error',
  : new ()
};

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 {
  : number;
  : string;
  ?: string;
  : number | null;
}

// Un type avec uniquement l'id et le nom
type  = <User, 'id' | 'name'>;

// Un type User sans l'id
type  = <User, 'id'>;

// Le type de 'age' sans la possibilité d'être 'null'
type  = <User['age']>;

// Le type de 'email' sans la possibilité d'être 'undefined'
type  = <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.