Découvrir TypeScript avec React
Introduction
TypeScript s’impose de plus en plus dans l’écosystème JavaScript. Si le coût de son implémentation est légèrement plus élevé à court terme (le code est plus verbeux), il apporte des gains massifs à long terme. Ainsi, même si cet ouvrage est principalement destiné au développement en React, il semble nécessaire de consacrer un chapitre à TypeScript, puisqu’il sera utilisé dans tous les chapitres suivants. Il est important de noter que le langage ne sera pas étudié exhaustivement, et qu’il est possible de faire de nombreuses autres choses qui ne seront pas démontrées ici, notamment en matière de programmation orientée objet (TypeScript dispose de tous les outils pour créer des classes, gérer la portée des attributs de classe, faire de l’héritage, déclarer des accesseurs, etc.). En effet, React impose un paradigme semi-fonctionnel, l’usage de classes, bien que requis dans les vieilles versions de React, est aujourd’hui remplacé par l’usage de composants fonctionnels. La plupart des développeurs peuvent donc désormais écrire du code en React sans n’avoir jamais eu affaire à des classes.
Qu’est-ce que TypeScript ?
TypeScript est un sur-ensemble (superset) de JavaScript, ce qui signifie que tout code JavaScript valide est également du code TypeScript valide. Cependant, l’inverse n’est pas vrai. L’objectif principal de TypeScript n’est pas de révolutionner la manière dont JavaScript est exécuté, mais plutôt d’apporter une couche de robustesse et de clarté supplémentaire à son développement.
Le code TypeScript est, par essence, une étape intermédiaire. Il est conçu pour être transpilé (converti) en code JavaScript. Ce processus est effectué par le compilateur TypeScript (tsc). Cela signifie que, une fois transpilé, le code TypeScript s’exécute comme du JavaScript classique dans n’importe quel environnement (navigateur, Node.js, etc.). Les bénéfices de TypeScript se manifestent lors des pratiques de développement en améliorant considérablement la lisibilité et la maintenabilité du code et en améliorant la collaboration au sein des équipes de développement.
Le cœur de TypeScript réside dans son système de typage statique. Contrairement à JavaScript qui est un langage dynamiquement typé (les types sont vérifiés à l’exécution), TypeScript permet...
Créer ses propres types
1. Le fonctionnement des types
En TypeScript, il existe deux manières principales de créer de nouveaux types. Soit en utilisant le mot-clé interface, soit en utilisant le mot-clé type. Les deux manières de faire sont très similaires et souvent interchangeables, mais étudier les deux reste requis puisque l’usage de l’un ou de l’autre mot-clé peut varier d’un projet ou d’une entreprise à l’autre.
Les interfaces et les types sont des outils fondamentaux pour définir la forme que doivent prendre les objets en TypeScript. Elles agissent comme des contrats, garantissant que les objets respectent une structure spécifique en matière de propriétés et, éventuellement, de méthodes. Dans le contexte de TypeScript, la déclaration d’un type ne génère aucun code JavaScript à l’exécution. Son rôle est purement informatif pour le compilateur TypeScript d’un côté, et pour le développeur de l’autre, permettant de s’assurer que les objets qui se conforment à cette interface possèdent bien toutes les propriétés attendues, avec les valeurs correctes. Un type est un peu comme un plan pour la construction d’un bâtiment. Le plan lui-même n’est pas le bâtiment, mais il spécifie exactement comment le bâtiment doit être construit (nombre de pièces, matériaux, etc.). Si vous construisez un bâtiment qui ne suit pas le plan, l’architecte (ici, le compilateur TypeScript) affichera une erreur.
2. Les interfaces
Pour déclarer une interface, on utilise le mot-clé interface, suivi du nom de l’interface (généralement en PascalCase) et d’un bloc de code entre accolades {} qui contient les propriétés et leurs types. Chaque propriété de l’interface se déclare au format name: type;.
À l’usage, la syntaxe pour déclarer une variable ayant le type de l’interface est identique à celle pour la déclaration des types primitifs. Simplement, puisqu’une interface est nécessairement un type objet, il faudra penser à renseigner un objet à l’assignation...
Les types génériques
1. Le principe de généricité avec le type Array<T>
La généricité n’est pas la partie la plus utilisée ni la plus connue de TypeScript. Néanmoins, dès qu’il est question de créer des composants en React et des types selon un standard professionnel, leur usage devient rapidement incontournable. TypeScript dispose d’un certain nombre de types dits « utilitaires » qui sont parfaits pour introduire la généricité.
Avant de rentrer plus en détail dans les types utilitaires et leurs effets, illustrons le principe de généricité avec un type primordial : Array<T>. Ce type est celui qui représente un tableau. Mais on voit qu’il est accompagné d’un <T>. Ce <T> indique que le type Array peut lui-même prendre un autre type en paramètre générique pour restreindre son contenu. Pour rendre cela plus concret, un tableau peut être vu comme une sorte de boîte. Par principe, on peut s’attendre à ce que cette boîte ne contienne qu’un certain type d’objets. Par exemple, il peut s’agir d’une caisse de pommes, ou d’une boîte de chocolats, ou d’une boîte à jouets. Ce <T> est précisément ce qui va permettre de spécifier ce que contient la boîte (le tableau). Grâce à la généricité, on bénéficie à la fois du typage statique fort de TypeScript (TypeScript sait et impose le contenu de la boîte) tout en garantissant la réutilisabilité du type Array pour n’importe quel type de boîte.
const numbers: Array<number> = [6, 8, -5, 3];
const strings: Array<string> = ["Hello", "world", "!"];
Ainsi, ajouter un nombre dans le tableau strings ou une chaîne de caractère dans le tableau numbers provoquera une erreur.
Le type Array<T> peut également s’écrire avec le nom du type suivi de crochets. number[] est équivalent à Array<number>. Il s’agit de sucre syntaxique pour le typage de tableaux.
2. Les types utilitaires de TypeScript
Nous savons désormais créer nos propres types. Toutefois...
La création de types génériques
1. Le cas d’utilisation simple
Dans la section précédente, il était principalement question de l’utilisation des types génériques natifs en TypeScript. Il existe des cas, bien que moins communs, où il est nécessaire de créer ses propres types génériques pour faire face à certaines problématiques et éviter d’avoir à dupliquer des morceaux de code.
Par exemple, une des opérations de base dans le développement web est la communication avec des API externes. La plupart du temps, les réponses d’une même API ont toujours la même forme. Supposons qu’elle ait trois propriétés : status, message et data. status et message auront toujours le même type. Toutefois, le type de data peut varier selon la route d’API qui est appelée. La tentation est grande de créer une interface BaseApiResponse qui contiendrait status et message, puis de créer autant d’interfaces que nécessaire pour chaque type de data via une forme d’héritage. Par exemple, ApiResponseUser, ApiResponseProduct, etc. Toutefois, il est évident que cette solution devient vite limitée lorsqu’on a affaire à un grand nombre de routes API, ce qui est souvent le cas sur les projets d’envergure.
C’est ici que le type générique peut nous sortir de cette situation : plutôt que de déclarer une à une les interfaces, il est préférable de déclarer un type unique ApiResponse<T>. T correspond au type de donnée contenu dans la réponse de la route d’API. Nous ne pouvons pas savoir à l’avance quels usages peuvent être faits de ce type, mais nous n’avons pas besoin de le savoir puisque ce type peut être renseigné au moment de l’appel API. Ainsi, au lieu de dire « cette propriété est de type string », le type générique permet d’affirmer « je ne connais pas à l’avance le type de cette propriété, mais il sera du type inconnu T ». Ensuite, lorsqu’on utilise le type générique, on remplace le type générique T par le type concret souhaité....