Les erreurs fréquentes à éviter en TypeScript

Conseils à ceux qui découvrent ou pour les passionnés

TypeScript est un langage à part entière, un sur-ensemble de JavaScript qui transforme la façon dont le code est écrit et compris dans le développement web. Depuis sa création par Microsoft en 2012, TypeScript a constamment gagné en popularité parmi les développeurs, offrant une alternative robuste et évolutive à JavaScript. En permettant le typage statique, il apporte une couche supplémentaire de sécurité et de prévisibilité au code, ce qui est particulièrement bénéfique dans les projets de grande envergure ou ceux nécessitant une maintenance à long terme.

L'adoption de TypeScript signifie plus qu'une simple transition de JavaScript vers un nouveau langage. Elle représente un changement fondamental dans l'approche de la programmation web. Le typage statique permet aux développeurs de définir explicitement les types de données utilisés dans leur code, réduisant ainsi les erreurs courantes telles que les fautes de frappe, les erreurs de type ou les problèmes liés à la manipulation de données non définies ou nulle. Cette précision supplémentaire se traduit par exemple par une maintenance simplifiée et une réduction significative des bugs qui pourraient autrement passer inaperçus jusqu'à l'exécution du code. Ainsi l’utilisation des types nécessite de formaliser les structures de données et les types qu’on manipule.

Cependant, maîtriser TypeScript ne se fait pas sans défis. Les développeurs doivent non seulement se familiariser avec ses concepts de base, mais aussi comprendre comment éviter des erreurs courantes qui peuvent diminuer les avantages offerts par le langage. Ces erreurs varient en complexité, touchant les développeurs débutants, intermédiaires et même experts.

En plus de ces défis liés à la maîtrise du langage lui-même, l'utilisation de TypeScript dans différents environnements de développement, tels que ReactJS, NextJS, Remix, ou Node.js, présente ses propres ensembles de pièges potentiels. Chaque framework ou environnement a ses spécificités d'intégration avec TypeScript, et les comprendre est crucial pour éviter des erreurs qui pourraient compromettre l'intégrité et la performance du code.

Cet article vise à explorer ces erreurs à différents niveaux d'expertise en TypeScript. En fournissant des exemples concrets pour chaque cas, nous souhaitons aider les développeurs à naviguer dans le paysage parfois complexe de TypeScript et à maximiser les avantages qu'il offre. Que vous soyez un débutant ou un expert chevronné, comprendre et éviter ces erreurs courantes est essentiel pour tirer le meilleur parti de TypeScript dans vos projets de développement web.

Cet article est divisé en deux parties, la première vous donnera les erreurs courantes liées à l’utilisation des types et la deuxième illustrera quelques pièges lors de l'utilisation de TypeScript dans différents environnements de développement, tels que ReactJS, NextJS, Remix, ou Nodejs.

I. Erreurs liées au typage et piège à éviter

A. Ignorer les Types de Base et l’utilisation excessive de « any »

L'utilisation systématique du type "any" pour toutes les variables est une erreur que les développeurs débutants font avec Typescript lorsqu’ils ont des difficultés à définir le type de ce qu’ils sont en train de manipuler. Imaginez que vous êtes en train de construire une maison. Vous avez une liste de matériaux spécifiques que vous pouvez utiliser : des briques, du bois, du ciment, etc. Chaque matériau a ses propres caractéristiques et utilisations spécifiques. Vous pourriez comparer ces matériaux aux différents types de données en TypeScript, tels que les chaînes de caractères, les nombres, les objets, etc.

Maintenant, supposons que vous vous retrouviez à court de certains matériaux spécifiques dont vous avez besoin pour terminer votre maison. Au lieu de prendre le temps de trouver le bon matériau, vous décidez simplement d'utiliser quelque chose que vous avez sous la main, peu importe ce que c'est. Peut-être que vous utilisez des boîtes en carton pour construire les murs, des couvertures pour le toit, et des sacs de sable pour le sol. Bien que vous ayez réussi à terminer la maison, elle est loin d'être solide, durable ou fonctionnelle. De plus, elle ne répond pas aux normes de construction et pourrait s'effondrer à tout moment.

Dans cet exemple, l'utilisation de "any" en TypeScript est similaire à l'utilisation de matériaux de construction de substitution dans votre maison. Plutôt que de spécifier le type de données correct pour vos variables, vous optez pour "any" simplement pour contourner les contraintes du système de types. Bien que cela puisse vous permettre de compiler votre code sans erreur, cela compromet la sûreté et la robustesse de votre application, tout comme une maison construite avec des matériaux de substitution compromet sa solidité et sa sécurité.

code non typé : variable non typé

Privilégier l'utilisation de types spécifiques tels que string, number, et boolean.

code typé : variable typé

Raisonnement : L'utilisation du type "any" annule les avantages du typage statique, ouvrant la porte à des erreurs subtiles. L'utilisation excessive du “any se produit souvent dans des cas plus complexes où l'usage generics seraient requis Spécifier des types précis pour les entrées et les sorties des fonctions.

code non typé : fonction prenant un tableau d'éléments non typés

code typé : image montrant une fonction avec des arguments typés

B. Mauvaise Utilisation des Types Union et Intersection

En TypeScript, les concepts d'union (« | ») et d'intersection (« & ») sont utilisés pour définir des types plus complexes en fonction de plusieurs types de base. Une union de types permet à une variable d'accepter des valeurs de plusieurs types différents tandis qu’une intersection de types crée un nouveau type qui a toutes les propriétés de chaque type.

code non typé : type User = { name: string } & { age: number }; Confusion dans le signe code typé : type User = { name: string } | { age: number };

Un exemple utilisant l’intersection : image montrant une mauvaise utilisation d'un type "union"

C. Utilisation erronée des types avancés

En TypeScript, les types avancés offrent des fonctionnalités plus complexes et puissantes pour décrire les types de données. Ces fonctionnalités offrent une flexibilité et une expressivité supplémentaires lors de la définition de types en TypeScript, permettant aux développeurs de créer des systèmes de types robustes et précis.

code non typé : variable d'énumération non typée

Dans cet exemple les valeurs affectées à la variable est un ensemble fini. une possibilité serait d’utiliser une approche différente.

code typé : variable de type avancée montrant un type énuméré typé

Il y a de nombreuses autres combinaisons pour créer des types avancés, on peut créer des types Nullable, des Assertions, des types génériques très souvent utilisés pour des fonctions, des types conditionnels etc. Par exemple, ne pas typer une fonction vous expose à des erreurs d’appel de cette dernière avec des mauvais paramètres. Dans ce cas, si les types d’entrée ne sont pas encore connu, la généricité semble être le meilleur remède, voici un petit exemple :

image montrant l'utilisation de la généricité

D. Ne pas typer le retour d’une fonction asynchrone

Lorsque vous déclarez le type de retour d'une fonction asynchrone qui renvoie une Promise en TypeScript, vous améliorez la clarté du code et fournissez des informations essentielles au compilateur TypeScript.

code non typé : async function getUser() { /* ... */ } code typé : async function getUser(): Promise<User> { /* ... */ }

La spécification du type de retour de la fonction asynchrone aide à clarifier l'intention du développeur. Cela agit comme une forme de documentation en indiquant le type de valeur que la fonction promet de renvoyer.

async function fetchData(): Promise<Data> {// ...}

En spécifiant le type de retour, TypeScript peut détecter d'éventuelles incohérences entre ce que la fonction est censée renvoyer et ce qu'elle renvoie réellement. Cela peut aider à repérer des erreurs potentielles dès la phase de développement.

image montrant une fonction qui retourne un type différent de celui spécifié en sortie

Certains outils de développement et éditeurs, comme Visual Studio Code, utilisent les informations de type pour fournir une expérience de développement plus riche. Les types spécifiés facilitent l'autocomplétion, la documentation en ligne et la navigation dans le code.

En spécifiant le type de retour, vous assurez que votre fonction respecte les contrats définis par des interfaces ou des types personnalisés. Cela favorise la cohérence et la conformité avec le reste du code.

image montrant une fonction qui retourne le bon type retourné

Bien que TypeScript puisse souvent inférer les types de manière implicite, spécifier explicitement le type de retour d'une fonction asynchrone renvoyant une Promise est une bonne pratique pour améliorer la lisibilité, détecter rapidement les erreurs et faciliter l'intégration avec d'autres parties du code.

E. La sous-utilisation des types utilitaires, qui peuvent parfois changer une vie 😊

Les types utilitaires sont des types génériques prédéfinis qui facilitent la manipulation et la transformation de types existants. Ces types utilitaires sont fournis par TypeScript pour simplifier les opérations courantes sur les types.

  1. Partial<T> : Rend toutes les propriétés d'un type ‘T’ données optionnelles en les marquant comme « ? ».
  2. Required<T> : Rend toutes les propriétés d'un type ‘T’ données obligatoires.
  3. Readonly<T> : Rend toutes les propriétés d'un type ‘T’ données en lecture seule.
  4. Record<K, T> : Crée un type avec des clés de type K et des valeurs de type T.
  5. Pick<T, K> : Extrait un sous-ensemble de propriétés d'un type ‘T’ en sélectionnant uniquement les propriétés dont les clés appartiennent à un ensemble de clés ‘K’
  6. Omit<T, K> : Exclut un sous-ensemble de propriétés d'un type ‘T’ à l'exception des propriétés dont les clés appartiennent à ‘K’.
  7. Exclude<T, U> : Exclut de T tous les types qui sont assignables à U

Ces types utilitaires offrent des moyens puissants et flexibles pour manipuler les types en TypeScript, améliorant ainsi la lisibilité, la maintenabilité et la sûreté du code. Vous pouvez également créer vos propres types utilitaires personnalisés pour répondre à des besoins spécifiques dans votre application.

L'utilisation des types utilitaires en TypeScript offre des avantages significatifs en termes de clarté, de maintenabilité et de sûreté du code. Ignorer ces utilitaires peut rendre le code plus sujet aux erreurs, plus difficile à maintenir et moins conforme aux meilleures pratiques de développement TypeScript.

F. La sur-utilisation des types primitifs

L'utilisation de types spécifiques plutôt que des types primitifs peut améliorer la lisibilité, la maintenabilité et l'évolutivité du code. La création d'un type englobant et une pratique très recommandée.

L'utilisation de types spécifiques rend le code plus expressif et facilite son évolution. Lorsqu'un développeur lit le code, il comprend immédiatement le rôle du type, ce qui facilite la compréhension globale du programme. Si le besoin évolue et que vous devez ajouter des informations supplémentaires au type, cela peut être fait facilement avec un type englobant sans impacter le reste du code.

A éviter : image montrant une sur-utilisation de type primitif__ __

A faire : variable utilisant une définition

L'utilisation de types englobants offre des avantages en termes de lisibilité, de maintenabilité, d'évolutivité et de détection d'erreur. Il peut également faciliter la collaboration entre développeurs en rendant le code plus compréhensible et en réduisant le risque d'erreurs liées aux types.

G. Négliger les Performances de Compilation

La création de types trop complexes peut augmenter le temps de compilation, par exemple : Image montrant des types récursifs

La création des types récursifs ayant plusieurs niveaux de profondeur peut causer une baisse de performance.

II. TypeScript et les framework JS

1. Typescript avec React

A. Mauvaise définition des Props et State

Erreur Commune : Omission de la définition des types pour les props et les états, ce qui peut mener à des erreurs de runtime imprévues.

code non typé :
image montrant un mauvais typage de props

code typé : Utiliser interface ou type pour définir clairement les props. image montrant une meilleur définition des props

Pour les composants avec des états complexes, définir des interfaces pour les états peut grandement améliorer la lisibilité et la maintenabilité du code. image montrant un l'utilisation d'une interface pour définir les "states" d'un composant

B. Utilisation Incorrecte des Hooks

Problème fréquent : Ne pas typer les hooks personnalisés, ce qui peut entraîner une confusion sur les types attendus en entrée et en sortie.

Code non typé : image d'une fonction qui ne type pas les paramètres d'entrées

Solution : Définir clairement les types de retour des hooks personnalisés.

Code typé : image d'une fonction ayant des paramètres d'entrée typésPour des hooks utilisant des contextes ou d'autres hooks React, s'assurer que les types sont cohérents à travers toute la chaîne d'utilisation.

Code typé : image montrant la cohérence de type dans un hook

2. Typescript avec NextJS

A. Typage Incorrect des Props de GetInitialProps ou GetServerSideProps

Erreur fréquente : Oublier de typer les données retournées par getInitialProps ou getServerSideProps, ce qui peut conduire à des problèmes de type au moment de l'exécution.

Code problématique : NextPage.getInitialProps = async (context) => { /* ... */ };

Solution : Définir clairement les types de retour pour ces fonctions.

image montrant la correction des types de retour des fonctions

B. API Routes et Typage

Piège courant : Ne pas typer les requêtes et les réponses dans les API routes, menant à des erreurs de manipulation des données.

Code non typé : route api avec paramètres typés

Solution : Utiliser les types NextApiRequest et NextApiResponse pour plus de clarté. code typé : fonction nextjs prenant en entrée des paramètres typés

3. Typescript avec Remix

A. Mauvaise Gestion des Loaders et Actions

Erreur typique : Manquer de typage sur les paramètres et les retours des loaders et actions, ce qui peut entraîner des problèmes de données.

Code non typé : image montrant une route avec des paramètre non typés

Correction : Définir les types pour les paramètres et les retours.

Code typé : image montrant une route avec des paramètre typés

B. Typage des Props de Composants

Défi Commun : Ignorer le typage des props passées aux composants Remix, ce qui peut mener à des erreurs de props manquantes ou incorrectes.

Code Initial : image d'un composant avec des props mal typés

Amélioration : Utiliser TypeScript pour typer les props de façon explicite. Correction : image d'un composant avec des props typés correctement

4. Typescript avec NodeJS

C. Typage des Modules et Importations

Erreur courante : Utiliser l'ancienne syntaxe CommonJS pour les modules, ce qui n'est pas idéal pour le typage TypeScript.

Ancienne écriture: image de l'ancienne écriture node

Solution Moderne : Adopter l'importation ES6 avec typage.

Code Modernisé : Nouvel import d'un module en es6

D. Gestion des Types dans les Middleware

Problème fréquent : Négliger de typer les paramètres des middlewares, menant à une confusion sur les types de requête et réponse.

Code non typé : middleware nodejs "use" ayant des paramètres non typés

Amélioration : Utiliser les types fournis par les packages comme express.

Code typé : middleware nodejs "use" ayant des paramètres typés

D. Types Personnalisés pour les Structures de Données

Défi habituel : Utiliser des types génériques pour des structures de données complexes, ce qui réduit la clarté du code.

Code générique : structure de donnée non typée

Solution : Créer des interfaces ou des types personnalisés.

Code Optimisé : structure de donnée typé avec une interface

Conclusion

En conclusion, naviguer dans le monde de TypeScript, qu'on soit débutant, intermédiaire ou avancé, peut présenter des défis spécifiques. Tout au long de cet article, nous avons exploré les erreurs courantes et les pièges qui peuvent survenir à chaque étape du parcours. La maîtrise de TypeScript nécessite une compréhension approfondie de ses fonctionnalités, des bonnes pratiques de développement et une vigilance constante pour éviter les pièges subtils.

Pour les débutants, il est crucial de bien assimiler les bases du typage statique et de comprendre comment TypeScript interagit avec JavaScript. Les erreurs de typage peuvent sembler déroutantes au départ, mais elles deviennent des alliées précieuses une fois que l'on maîtrise les subtilités du système de types.

Les développeurs intermédiaires doivent rester attentifs aux pièges liés à l'utilisation de types génériques, aux unions et aux intersections, tout en approfondissant leur compréhension des types avancés. La modularité du code et la gestion des dépendances deviennent également des éléments clés pour maintenir un code robuste et éviter les écueils.

Pour les développeurs avancés, l'optimisation des performances du compilateur, l'utilisation judicieuse des types utilitaires, et la création de types génériques complexes deviennent des compétences essentielles. La collaboration avec d'autres développeurs et la contribution à des projets open source nécessitent une maîtrise approfondie de TypeScript.

Quel que soit le niveau de compétence, l'apprentissage de TypeScript est un voyage continu. Les erreurs sont inévitables, mais chacune représente une opportunité d'apprentissage. En évitant ces pièges, en comprenant leurs causes et en adoptant les bonnes pratiques, les développeurs peuvent créer des applications plus fiables, maintenables et évolutives en TypeScript.

Restez curieux, continuez à explorer et n'ayez pas peur d'affronter les défis que TypeScript peut présenter. À mesure que vous perfectionnez vos compétences, vous découvrirez la puissance de ce langage et la satisfaction qui vient avec la création de logiciels robustes et élégants. Happy coding! 🚀✨