Typescript est un outil formidable. Il offre de nombreuses fonctionnalités intéressantes. Voici un résumé de quelques-unes des plus grandes fonctionnalités avancées que nous allons découvrir dans cet article :

  • Types d'union et d'intersection
  • Keyof
  • Typeof
  • Types conditionnels
  • Types d'utilité
  • Type d'inférence
  • Types mappés

À la fin de ce billet, vous devriez avoir une compréhension de base de chacun de ces opérateurs et vous devriez être en mesure de les utiliser dans vos projets.
les utiliser dans vos projets.

Types d'union et d'intersection

TypeScript nous permet de combiner plusieurs types pour en créer un nouveau. Cette approche est similaire aux expressions logiques en JavaScript, où nous pouvons utiliser l'opérateur logique OU (||) ou l'opérateur logique ET (&&) pour créer des vérifications puissantes.

Types d'union

Un type d'union est similaire à l'expression OU (||) en JavaScript. Il vous permet d'utiliser deux types ou plus (membres de l'union) pour former un nouveau type qui peut être l'un ou l'autre de ces types.

function orderProduct(orderId: string | number) {
  console.log('Ordering product with id', orderId);
}

// 👍
orderProduct(1);

// 👍
orderProduct('123-abc');

// 👎 Argument is not assignable to string | number
orderProduct({ name: 'foo' });

Nous typons la méthode orderProduct avec un type d'union. TypeScript générera une erreur si nous appelons la méthode orderProduct avec une valeur qui n'est ni un nombre ni une chaîne de caractères.

Types d'intersection

Un type d'intersection, quant à lui, combine plusieurs types en un seul. Ce nouveau type possède toutes les fonctionnalités des types combinés.

interface Person {
  name: string;
  firstname: string;
}

interface FootballPlayer {
  club: string;
}

function tranferPlayer(player: Person & FootballPlayer) {}

// 👍
transferPlayer({
  name: 'Ramos',
  firstname: 'Sergio',
  club: 'PSG',
});

// 👎 Argument is not assignable to Person & FootballPlayer
transferPlayer({
  name: 'Ramos',
  firstname: 'Sergio',
});

La méthode transferPlayer accepte un type qui contient toutes les fonctionnalités à la fois de Person et de FootballPlayer. Seul un objet contenant les propriétés name, firstname et club sera valide.

Keyof

Maintenant que nous connaissons les types d'union, examinons l'opérateur keyof. L'opérateur keyof prend les clés d'une interface ou d'un objet et produit un type d'union.

interface MovieCharacter {
  firstname: string;
  name: string;
  movie: string;
}

type characterProps = keyof MovieCharacter;

D'accord, mais à quoi cela sert-il ? Nous pourrions aussi simplement taper characterProps manuellement.

type characterProps = 'firstname' | 'name' | 'movie';

Oui, c'est possible. Cependant, keyof rend notre code plus robuste et maintient toujours nos types à jour. Explorons cela avec l'exemple suivant.

interface PizzaMenu {
  starter: string;
  pizza: string;
  beverage: string;
  dessert: string;
}

const simpleMenu: PizzaMenu = {
  starter: 'Salad',
  pizza: 'Pepperoni',
  beverage: 'Coke',
  dessert: 'Vanilla ice cream',
};

function adjustMenu(
  menu: PizzaMenu,
  menuEntry: keyof PizzaMenu,
  change: string,
) {
  menu[menuEntry] = change;
}

// 👍 Exemple valide
adjustMenu(simpleMenu, 'pizza', 'Hawaii');
// 👍 Exemple valide
adjustMenu(simpleMenu, 'beverage', 'Beer');

// 👎 Erreur de type - 'bevereger' n'est pas assignable
adjustMenu(simpleMenu, 'bevereger', 'Beer');
// 👎 Propriété incorrecte - 'coffee' n'est pas assignable
adjustMenu(simpleMenu, 'coffee', 'Beer');

La fonction adjustMenu permet de modifier un menu. Par exemple, imaginons que vous aimiez le menu simpleMenu, mais que vous préfériez boire de la bière plutôt qu'un Coca. Dans ce cas, nous appelons la fonction adjustMenu avec le menu, l'entrée du menu (menuEntry) et le changement, dans notre cas, une bière.

La partie intéressante de cette fonction est que menuEntry est typé avec l'opérateur keyof. L'avantage ici est que notre code est très robuste. Si nous refactorisons l'interface PizzaMenu, nous n'avons pas besoin de modifier la fonction adjustMenu. Elle reste toujours synchronisée avec les clés de PizzaMenu.

Typeof

L'opérateur typeof vous permet d'extraire un type à partir d'une valeur. Il peut être utilisé dans un contexte de type pour faire référence au type d'une variable.

let firstname = 'Frodo';
let name: typeof firstname;

Bien sûr, cela n'a pas beaucoup de sens dans des scénarios aussi simples. Mais examinons un exemple plus sophistiqué. Dans cet exemple, nous utilisons typeof en combinaison avec ReturnType pour extraire des informations de typage à partir du type de retour d'une fonction.

function getCharacter() {
  return {
    firstname: 'Frodo',
    name: 'Baggins',
  };
}

type Character = ReturnType<typeof getCharacter>;

/*

Équivalent à :

type Character = {
  firstname: string;
  name: string;
}

*/

Dans l'exemple ci-dessus, nous créons un nouveau type basé sur le type de retour de la fonction getCharacter. Comme précédemment, si nous refactorisons le type de retour de cette fonction, notre type Character reste automatiquement à jour.

Types conditionnels

L'opérateur ternaire conditionnel est un opérateur très connu en JavaScript. L'opérateur ternaire prend trois opérandes : une condition, un type de retour si la condition est vraie, et un type de retour si la condition est fausse.

condition ? returnTypeIfTrue : returnTypeIfFalse;

Le même concept existe également en TypeScript.

interface StringId {
  id: string;
}

interface NumberId {
  id: number;
}

type Id<T> = T extends string ? StringId : NumberId;

let idOne: Id<string>;
// équivalent à let idOne: StringId;

let idTwo: Id<number>;
// équivalent à let idTwo: NumberId;

Dans cet exemple, nous utilisons le type Id pour générer un type en fonction d'une chaîne de caractères. Si T est une string, nous retournons le type StringId. Si nous passons un number, nous retournons le type NumberId.

Types utilitaires

Les types utilitaires sont des outils d'aide permettant de faciliter les transformations de types courantes. TypeScript propose de nombreux types utilitaires, trop nombreux pour être tous couverts dans cet article. Ci-dessous, vous trouverez une sélection de ceux que j'utilise le plus fréquemment.

La documentation officielle de TypeScript propose une excellente liste de tous les types utilitaires.

Le type utilitaire Partial

Le type utilitaire Partial permet de transformer une interface en une nouvelle interface où toutes les propriétés deviennent optionnelles.

interface MovieCharacter {
  firstname: string;
  name: string;
  movie: string;
}

function registerCharacter(character: Partial<MovieCharacter>) {}

// 👍
registerCharacter({
  firstname: 'Frodo',
});

// 👍
registerCharacter({
  firstname: 'Frodo',
  name
})