Tuto montrant des pièges classiques TypeScript et comment les éviter en utilisant des bonnes pratiques de codage TS, pour un code plus solide

Comment éviter les pièges courants en TypeScript : 5 erreurs à ne pas faire !

TypeScript est un outil puissant pour écrire du code robuste ; mais il peut aussi vous piéger, si vous n’y prêtez pas attention ! Entre abus de any, mauvaises pratiques de typage, et erreurs subtiles, il y a bon nombre de pièges à connaître, afin de pouvoir les éviter. Dans cet article, nous allons voir 5 erreurs courantes en TypeScript, avec des solutions pour les contourner, et coder "comme un pro" !

L'objectif étant, avec cet article, d'identifier des pièges TypeScript courants, et de vous partager de bonnes pratiques à suivre. Ça vous dit ? Si oui, alors bonne lecture à vous 🙂

Remarque : n'hésitez pas également à faire un saut dans la rubrique "Astuces et Bonnes pratiques", pour apprendre à "mieux" coder en TypeScript, et ainsi, éviter tout un tas d'autres pièges TS classiques !

Salut ! Bienvenue sur LeCoinTS, où je partage des tutos TypeScript gratuits et sans pub. Envie de me soutenir dans cette aventure, avec un café ? ☕

Offrez moi un café LeCoinTS

Piège n°1 : Abuser de "any", et perdre les avantages de TypeScript

Basiquement, le type any permet de désactiver toute vérification de type, au niveau d'une variable. Et on est souvent tenté de le faire, pour aller vite et gagner du temps, ou sur des types complexes. Mais utiliser any revient à écrire du JavaScript pur, et donc, à enlever tout l'intérêt de TypeScript.

Prenons ce code en exemple :

function afficherDonnees(donnees: any): void {
  console.log(donnees.toUpperCase());
}

afficherDonnees("Bonjour !");
// Aucune erreur signalée par TypeScript
// Aucune erreur à l'exécution (affichage d'un "BONJOUR !" dans la console ; en majuscule, grâce à .toUpperCase())

afficherDonnees(123456);
// Aucune erreur signalée par TypeScript
// Crash à l'exécution (car le programme essaye de faire un "123456.toUpperCase()",
//                      ce qui n'est pas possible de faire sur un nombre)

Si vous copiez/collez ce programme dans votre éditeur de code, vous verrez qu'aucune erreur ne sera remontée par TypeScript (du fait du "any"). Pourtant, au moment de l'exécution, vous aurez un crash programme, au niveau de la commande afficherDonnees(123456).

La solution, pour contourner ce problème, est comme vous vous en doutez de toujours bien typer ses variables/paramètres comme il faut ! Ici, dans notre code, il aurait fallut mettre string à la place de any, car la méthode toUpperCase() ne peut s'appliquer que sur un string.

En clair, voici ce qu'il aurait fallu écrire :

function afficherDonnees(donnees: string): void {
  console.log(donnees.toUpperCase());
}

afficherDonnees("Bonjour !");
// Aucune erreur signalée par TypeScript

afficherDonnees(123456);
// ERREUR immédiatement signalée par TypeScript, et ce, AVANT exécution
//    (message d'erreur : Argument of type 'number' is not assignable to parameter of type 'string')

C'est mieux comme ça, non ? 😉

En tous cas, typer les choses à fond est le genre de bonne pratique qui paye ensuite ! Bon là, sur un exemple simple, c'est pas si évident. Mais dans des programmes plus conséquents, vous pouvez ainsi générer des crash à des moments inattendus si vous utilisez any "les yeux fermés". Du coup : même si c'est très tentant, évitez à tout prix le type any ! Même si c'est pénible et contraignant au début, faites l'effort ! Car ça payera ensuite, comme on dit !

Par contre, rien ne vous empêche d'utiliser unknown à la plage d'any, pour dire que le type de l'élément envoyé est inconnu, du moment où vous testez ce type ensuite. C'est d'ailleurs une autre bonne pratique, au passage. Sur notre code, voici ce que ça aurait donné :

function afficherDonnees(donnees: unknown): void {
  if(typeof donnees === "string")
    console.log(donnees.toUpperCase());
  else
    console.log("Impossible d'afficher les données, car elles ne sont pas de type string");
}

afficherDonnees("Bonjour !");
// Retourne : "BONJOUR !" 

afficherDonnees(123456);
// Retourne : "Impossible d'afficher les données, car elles ne sont pas de type string"

Là, aucune erreur à la compilation TypeScript, et aucune erreur à l'exécution ! Car nous avons parfaitement "géré l'inconnu" avec notre "unknown" suivi d'un test "typeof" en suivant ! Du coup, vous avez là une solution "élégante" pour bien gérer vos typages, et ce, en évitant un hasardeux "any" 😉

Piège n°2 : Ignorer les erreurs de type Warning, lors de compilation

TypeScript est strict pour une raison : il repère tout un tas de problèmes potentiels AVANT l’exécution d'un programme . Du coup, ignorer d'éventuels avertissements TypeScript (de type "warning errors"), comme celles signalées par ESlint n'est pas prudent ! Surtout que ça peut mener à des bugs imprévus !

En pratique d'ailleurs, on tombe souvent dessus lorsqu'on traite le retour JSON d'une API (où là, le type réponse retournée par l'API n'est pas connu dans notre code TS). Voici un exemple l'illustrant assez bien (c'est un programme qui affiche la température à des coordonnées GPS données) :

const afficheDonneesMeteoSuivantCesCoordonnesGPS = async (latitude: number, longitude: number): Promise<void> => {
  const response = await fetch(`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current_weather=true`, {
    method: "GET",
  })
  const result = await response.json()

  // TypeScript voit `result` comme `any` car aucune info de type n'est fournie par défaut

  const valeurTemperature = result.current_weather.temperature // Erreur potentielle sans typage
  const uniteTemperature = result.current_weather_unit.temperature // Erreur potentielle sans typage

  console.log("Température :", valeurTemperature, uniteTemperature)
}

afficheDonneesMeteoSuivantCesCoordonnesGPS(48, 0)

Dans cet exemple, nous interrogeons une API météo, puis traitons les infos qu'elle nous retourne, pour en extraire la température (valeur numérique) et l'unité de mesure de cette température (°C ou °F) ; et ce, aux coordonnées GPS qu'on a renseigné. Or, comme vous pouvez le constater dans cet exemple, on ne connaît pas la structure exacte retournée par cette API, au niveau de notre code. Du coup, si on fait la moindre erreur de ciblage de propriété (ici sur current_weather.temperature ou current_weather_unit.temperature), eh bien c'est la cata 🙂

D'ailleurs, j'ai volontairement glissé une erreur ici (car il faut un "s" à la fin de current_weather_unit, en réalité), pour vous montrer que TypeScript ne peut pas deviner ce que l'API devait retourner, et que par conséquent, TypeScript ne peut pas anticiper une erreur à ce niveau.

Si j'exécute ce programme en l'état sous VS Code, on obtient un crash programme en cours d'exécution (vous noterez au passage les "alertes" d'ESLint soulignées en orange, signalant des potentiels problèmes) :

Exemple de crash programme TypeScript avec problème de fetch GET sous VS Code, pour apprentissage TS et bonnes pratiques de codage

Et le programme crash car, à moment donné, il ne trouve pas la propriété "temperature" au niveau de l'instruction const uniteTemperature = result.current_weather_unit.temperature (ce qui est normal, puisqu'il faut un "s" à "units", et là j'ai volontairement écrit "unit").

Du coup, pour éviter ce genre d'erreur de nommage manuel dans les propriétés, préférez plutôt prendre la structure exacte retournée par l'API, pour la transformer ensuite en interface. Par exemple, si j'interroge l'API météo sur mon navigateur, à l'adresse https://api.open-meteo.com/v1/forecast?latitude=48&longitude=0&current_weather=true (avec une latitude et longitude prise au hasard, simplement pour récupérer la structure de la réponse), alors j'obtiens la réponse suivante :

{
  "latitude": 48,
  "longitude": -2.3841858e-7,
  "generationtime_ms": 0.0369548797607422,
  "utc_offset_seconds": 0,
  "timezone": "GMT",
  "timezone_abbreviation": "GMT",
  "elevation": 113,
  "current_weather_units": {
    "time": "iso8601",
    "interval": "seconds",
    "temperature": "°C",
    "windspeed": "km/h",
    "winddirection": "°",
    "is_day": "",
    "weathercode": "wmo code"
  },
  "current_weather": {
    "time": "2025-03-31T16:00",
    "interval": 900,
    "temperature": 16.3,
    "windspeed": 15.5,
    "winddirection": 59,
    "is_day": 1,
    "weathercode": 0
  }
}

Ainsi, on peut reprendre cette réponse JSON pour en faire une interface TypeScript (en remplaçant toutes les valeurs numériques par number, et toutes les valeurs texte par string, grosso-modo). Au final, si on met à jour notre programme précédent en rajoutant une interface pour bien cadrer les choses, le code TS devient bien plus solide :

interface meteoDataInterface {
  latitude: number
  longitude: number
  generationtime_ms: number
  utc_offset_seconds: number
  timezone: string
  timezone_abbreviation: string
  elevation: number
  current_weather_units: {
    time: string
    interval: string
    temperature: string
    windspeed: string
    winddirection: string
    is_day: string
    weathercode: string
  }
  current_weather: {
    time: string
    interval: number
    temperature: number
    windspeed: number
    winddirection: number
    is_day: number
    weathercode: number
  }
}

const afficheDonneesMeteoSuivantCesCoordonnesGPS = async (latitude: number, longitude: number): Promise<void> => {
  const response = await fetch(`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current_weather=true`, {
    method: "GET",
  })
  const result: meteoDataInterface = await response.json()
  const valeurTemperature = result.current_weather.temperature
  const uniteTemperature = result.current_weather_units.temperature

  console.log("Température :", valeurTemperature, uniteTemperature)
}

afficheDonneesMeteoSuivantCesCoordonnesGPS(48, 0)

Là, aucun avertissement TypeScript, et tout se passe bien au moment de l'exécution (ça m'a retourné "Température : 16 °C" de mon côté, au moment où j'ai écrit ces lignes !).

À retenir ici, donc : prenez toujours le temps de bien comprendre les avertissements TS, et de les corriger comme il faut ! Ces avertissements (soulignés en orange) de TypeScript/ESLint ne sont pas là pour rien : les ignorer peut crasher votre code à des moments particuliers !

Vous aimez cet article ? LeCoinTS reste gratuit et sans pub grâce à vos dons.
Motivez-moi à en faire plus ! ☕

Offrez moi un café LeCoinTS

Piège n°3 : Mal utiliser "as" (type assertion)

Les assertions de type as permettent de forcer un type (cela permet par exemple de dire que telle ou telle variable serait de tel ou tel type). Utilisé de la bonne manière, cela peut nous servir ; mais parfois, utiliser "as" peut masquer certaines erreurs, que l'on ne détectera pas toujours de suite.

Un exemple, pour que ce soit plus parlant (en provoquant un crash programme, pour que le problème soit bien visible !) :

interface UtilisateurInterface {
  id: number
  nom: string
}

const donnees = { id: 1 }

const utilisateur: UtilisateurInterface = donnees as UtilisateurInterface

console.log(utilisateur.nom.toUpperCase()) // Pas d’erreur remontée par TypeScript, mais provoquera un crash programme

Ici, on a une variable utilisateur dans laquelle on va mettre la valeur contenue dans la variable donnees, tout en précisant (par forçage) que cette dernière est de type UtilisateurInterface. Comme vous vous en doutez, vu que "donnees" ne contient que la propriété "id", et que l'interface requiert un "id" et un "nom", ça va mal se terminer, à un moment ou un autre !

Voici d'ailleurs le crash bien visible à l'écran, à l'exécution de ce programme sous VS Code (à noter qu'au moment de la compilation, TypeScript ne remonte pas la moindre erreur, car nous avons volontairement forcé les choses avec le mot clef "as" ; du coup, pas d'erreur signalable par TS) :

Crash programme node sur propriété undefined dans vscode, exemple pour apprendre TypeScript avec astuces et bonnes pratiques pour éviter erreurs

Lorsque ce programme s'exécute, on essaye d'afficher le nom utilisateur en majuscule. Or, ce nom n'existait pas dans nos données (on avait que l'id) ; il est donc à l'état "undefined", pour ainsi dire. Du coup, lorsqu'on essaye de mettre le nom en majuscule, le code essaye en fait d'exécuter la commande "undefined.toUpperCase()" ; ce qui crash le programme, car la fonction "toUpperCase" ne peut s'appliquer que sur un type string (et évidemment pas sur un undefined !).

En fait, si on n'avait pas forcé le type avec as UtilisateurInterface (retirez le, et vous verrez la différence !), alors TypeScript aurait immédiatement signalé le problème en amont (avec une erreur, au niveau de la variable utilisateur, du style : Property 'nom' is missing in type '{ id: number; }' but required in type 'UtilisateurInterface').

N'utilisez donc "as" que lorsque vous êtes certain du type (lors de la récupération de données en base de données, par exemple). Sinon laissez de côté, ou alors, faites tout un tas de vérifications approfondies en suivant. Voici d'ailleurs comment on aurait pu renforcer l'exemple précédent en ce sens, pour "parer tout problème" tout en gardant as :

interface UtilisateurInterface {
  id: number
  nom: string
}

const donnees = { id: 1 }

const utilisateur: UtilisateurInterface = donnees as UtilisateurInterface

if ("nom" in utilisateur && typeof utilisateur.nom === "string") {
  console.log(utilisateur.nom.toUpperCase())
} else {
  console.log("Erreur : la propriété 'nom' est manquante ou non valide")
}

Là tout se passera bien, car nous vérifions si la propriété "nom" existe bien, et si elle est bien de type "string" (indispensable pour pouvoir exécuter le "toUpperCase" qui arrive ensuite). Dans notre cas, le programme affichera : "Erreur : la propriété 'nom' est manquante ou non valide", car la propriété "nom" était déjà manquante dans la variable "donnees".

Piège n°4 : Désactiver le mode strict (ou oublier de le mettre)

Le mode strict de TypeScript (qui s'inscrit dans le fichier tsconfig.json) impose des règles plus rigoureuses que d'habitude, en quelque sorte. Mais certains débutants ne l’activent pas, ou le désactivent simplement par commodité, ce qui mène tôt ou tard à des erreurs programme.

Voici un exemple le démontrant (nota : j'ai mis "strict": false dans mon fichier tsconfig.json à mon niveau, pour vous montrer ce qu'il se passe lorsque le mode strict n'est pas activé) :

function saluer(nom: string): void {
  console.log(`Salut ${nom} !`)
}

const nom: string | null = null
saluer(nom) // Aucune erreur à la compilation TS, mais à l’exécution, on affiche le message : "Salut null !"

Lorsqu'on lance ce programme, on obtient le message "Salut null !" dans la console. Donc le programme s'exécute bien, mais affiche n'importe quoi !

En fait, ce qu'il faut savoir, c'est que sans strictNullChecks actif (inclus dans le mode strict), TS ne se forcera pas à vérifier les éventuels null ou undefined, présents dans le code, comme c'est le cas ici. Et si on réactive le mode strict dans tsconfig.json, l'erreur est clairement signalée par TypeScript, au niveau de "saluer(nom)" : Argument of type 'null' is not assignable to parameter of type 'string'.

Donc toujours activer le mode strict dans votre fichier tsconfig.json, enfin… c'est mon conseil ! Et pour ceux qui ne sauraient pas ce qu'est un fichier de configuration tsconfig.json ou comment le configurer en ce sens, voici un exemple de fichier "tsconfig.json" ultra minimal, pour gérer l'option "strict" (fichier à créer à la racine de votre projet, si inexistant) :

{
  "compilerOptions": {
    "strict": true
  }
}

Avec ça, on renforce les contrôles TypeScript grandement !

Remarque : si vous souhaitez un exemple un peu plus approfondi de fichier tsconfig.json, n'hésitez pas à faire un saut ici : configurer le compilateur "tsc" pour un typescript au top !.

Piège n°5 : Ne pas gérer correctement les types optionnels et les valeurs undefined

Bon… un dernière piège pour la route ! À noter qu'il va un peu faire redondance avec le piège n°4, mais cela me permettra de vous montrer d'autres choses !

Alors, avant de voir le programme de ce cinquième piège TS, il faut se rappeler que TypeScript permet de rendre optionnels des propriétés ou paramètres, avec le sigle "?". Mais dans ce cas, il ne faut surtout pas oublier de gérer les cas où ces valeurs sont undefined. Car cela peut mener à des erreurs d’exécution, si par exemple vous n'avez pas mis "strict": true, dans la section compilerOptions de votre fichier tsconfig.json.

Voici un exemple l'illustrant, d'ailleurs :

interface Utilisateur {
  nom: string
  age?: number // Optionnel
}

const afficherAge = (user: Utilisateur): string => {
  // On suppose que age existe
  return `${user.nom} a ${user.age.toString()} ans`
  // -----> Crash programme, car "age" est undefined
}

const user: Utilisateur = { nom: "Alice" } // Pas d’age
console.log(afficherAge(user))

Et l'aperçu de ce qu'il se passe, lorsqu'on exécute ce code en l'état (je vous ai rajouté mon fichier tsconfig.json sur la droite de l'image, pour bien mettre en évidence les incidences d'un "strict": false dedans) :

Exemple problème undefined si strict = false dans fichier tsconfig.json, exemple didactique pour apprendre le TypeScript avec des codes pratiques

Le problème est que sans vérification approfondie, TypeScript n'empêche pas d’accéder à une propriété optionnelle qui serait potentiellement undefined. Du coup, votre code peut planter, en cours d'exécution. À noter que si "strict" avait été mis sur true, alors on aurait eu un avertissement, nous disant que la valeur user.age serait possiblement undefined.

Nous allons donc mettre "strict": true dans le fichier tsconfig.json, pour que TypeScript nous alerte sur ce possible problème à venir. Ensuite, nous allons compléter notre programme, pour gérer ce cas où le nom serait undefined. Voici ce que ça donne, à présent (j'ai au passage rajouté un utilisateur, pour tester le retour console) :

interface Utilisateur {
  nom: string
  age?: number // Optionnel
}

const afficherAge = (user: Utilisateur): string => {
  if (user.age !== undefined)
    return `${user.nom} a ${user.age.toString()} ans`
  else
    return `${user.nom} n'a pas précisé son âge`
}

const user1: Utilisateur = { nom: "Alice" } // Pas d’âge renseigné
console.log(afficherAge(user1))

const user2: Utilisateur = { nom: "Bertrand", age: 30 } // Avec l'âge renseigné
console.log(afficherAge(user2))

Ainsi, "toutes les possibilités" sont à présent traitées dans notre code ! Du coup il n'y aura pas de crash programme, que la valeur "age" soit présente ou non (undefined, dans ce cas). Voici ce que ça donne, dans VS Code, avec ce code corrigé :

Exemple de prise en charge undefined dans le code TS sous VSCode, en vue de l'apprentissage des bonnes pratiques TypeScript avec programmes

Là, la console nous retourne :

  • "Alice n'a pas précisé son âge"
  • "Bertrand a 30 ans"

Voilà, tout roule maintenant 🙂

Conclusion

Pour résumer l'idée derrière tous ces pièges possibles, je voulais souligner le fait que TypeScript est notre allié ! Et bien l'utiliser rend notre code super costaud ! Donc évitez les "any", corrigez tous les avertissements (de type warning / souligné en orange) dans le code, attention lorsque vous utilisez le mot clef "as", et gérez bien "tous les cas" (surtout null et undefined). Avec ces bonnes pratiques, votre code sera plus sûr et vos projets plus robustes !

Du reste, je vous ai préparé un exemple de mini chatbot en TypeScript, afin de vous montrer un exemple pratique de projet TS ! Vous allez voir, c'est sympa ! Et si tout ce contenu TypeScript vous plait, n'hésitez pas à me le faire savoir, en m'offrant un café ! (en cliquant sur le bouton jaune, ci-dessous)

Merci à vous,
Jérôme.

Merci d’avoir lu cet article ! LeCoinTS reste gratuit et sans pub grâce à vous.
Un petit don pour soutenir le site ? ☕

Offrez moi un café LeCoinTS
Site LeCoinTS.fr

JEROME

Passionné par tout ce qui touche à la programmation informatique en TypeScript, sans toutefois en être expert, j'ai à coeur de vous partager ici, peu à peu, tout ce que j'ai appris, découvert, réalisé, et testé jusqu'à présent ! En espérant que tout cela puisse vous servir, ainsi qu'au plus grand nombre de francophones possible !

(*) Mis à jour le 14/04/2025

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Résolvez cette soustraction :

32 - ____ = 24

Soutenir LeCoinTS

×

Soutenez le site LeCoinTS en offrant un café symbolique, afin que celui-ci reste 100% gratuit et sans pub, et pour me motiver à faire d'autres articles !
1 café = 5 € environ
Plusieurs cafés = au top !!!

→ Via Logo PayPal
Soutenir avec PayPal
→ Via Logo Stripe
Soutenir avec Stripe
Merci à vous ❤️