paint-brush
Libérer la puissance de TypeScript : considérations clés dans tsconfigpar@nodge
2,700 lectures
2,700 lectures

Libérer la puissance de TypeScript : considérations clés dans tsconfig

par Maksim Zemskov13m2023/07/12
Read on Terminal Reader

Trop long; Pour lire

TypeScript est un langage populaire pour la création d'applications complexes, grâce à son système de typage puissant et à ses capacités d'analyse statique. Cependant, pour obtenir une sécurité de type maximale, il est important de configurer correctement tsconfig. Dans cet article, nous aborderons les considérations clés pour la configuration de tsconfig afin d'obtenir une sécurité de type optimale.
featured image - Libérer la puissance de TypeScript : considérations clés dans tsconfig
Maksim Zemskov HackerNoon profile picture
0-item
1-item

Si vous créez des applications Web complexes, TypeScript est probablement votre langage de programmation de choix. TypeScript est très apprécié pour son système de typage puissant et ses capacités d'analyse statique, ce qui en fait un outil puissant pour garantir que votre code est robuste et sans erreur.


Il accélère également le processus de développement grâce à l'intégration avec les éditeurs de code, permettant aux développeurs de naviguer plus efficacement dans le code et d'obtenir des conseils et une auto-complétion plus précis, tout en permettant une refactorisation sûre de grandes quantités de code.


Le compilateur est le cœur de TypeScript, responsable de la vérification de l'exactitude du type et de la transformation du code TypeScript en JavaScript. Cependant, pour utiliser pleinement la puissance de TypeScript, il est important de configurer correctement le compilateur.


Chaque projet TypeScript possède un ou plusieurs fichiers tsconfig.json qui contiennent toutes les options de configuration du compilateur.


La configuration de tsconfig est une étape cruciale pour obtenir une sécurité de type et une expérience de développement optimales dans vos projets TypeScript. En prenant le temps d'examiner attentivement tous les facteurs clés impliqués, vous pouvez accélérer le processus de développement et vous assurer que votre code est robuste et sans erreur.

Inconvénients de la configuration standard

La configuration par défaut dans tsconfig peut faire perdre aux développeurs la majorité des avantages de TypeScript. En effet, il ne permet pas de nombreuses fonctionnalités puissantes de vérification de type. Par configuration "par défaut", j'entends une configuration dans laquelle aucune option du compilateur de vérification de type n'est définie.


Par exemple:


 { "compilerOptions": { "target": "esnext", "module": "esnext", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "skipLibCheck": true, }, "include": ["src"] }


L'absence de plusieurs options de configuration clés peut entraîner une qualité de code inférieure pour deux raisons principales. Premièrement, le compilateur de TypeScript peut gérer de manière incorrecte les types null et undefined dans divers cas.


Deuxièmement, any type peut apparaître de manière incontrôlable dans votre base de code, entraînant une vérification de type désactivée autour de ce type.


Heureusement, ces problèmes sont faciles à résoudre en modifiant quelques options dans la configuration.

Le mode strict

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


Le mode strict est une option de configuration essentielle qui offre des garanties plus solides d'exactitude du programme en permettant un large éventail de comportements de vérification de type.


L'activation du mode strict dans le fichier tsconfig est une étape cruciale pour atteindre une sécurité de type maximale et une meilleure expérience de développement.


Cela nécessite un petit effort supplémentaire pour configurer tsconfig, mais cela peut grandement améliorer la qualité de votre projet.


L'option de compilateur strict active toutes les options de la famille de mode strict, qui incluent noImplicitAny , strictNullChecks , strictFunctionTypes , entre autres.


Ces options peuvent également être configurées séparément, mais il n'est pas recommandé de les désactiver. Regardons des exemples pour voir pourquoi.

Implicite Toute déduction

 { "compilerOptions": { "noImplicitAny": true } }


Le type any est une faille dangereuse dans le système de type statique, et son utilisation désactive toutes les règles de vérification de type. En conséquence, tous les avantages de TypeScript sont perdus : des bogues sont manqués, les conseils de l'éditeur de code cessent de fonctionner correctement, etc.


L'utilisation any n'est acceptable que dans des cas extrêmes ou pour des besoins de prototypage. Malgré tous nos efforts, any type peut parfois se faufiler implicitement dans une base de code.


Par défaut, le compilateur nous pardonne beaucoup d'erreurs en échange de l'apparition de any dans une base de code. Plus précisément, TypeScript nous permet de ne pas spécifier le type des variables, même lorsque le type ne peut pas être déduit automatiquement.


Le problème est que nous pouvons accidentellement oublier de spécifier le type d'une variable, par exemple, à un argument de fonction. Au lieu d'afficher une erreur, TypeScript déduira automatiquement le type de la variable comme any .


 function parse(str) { // ^? any return str.split(''); } // TypeError: str.split is not a function const res1 = parse(42); const res2 = parse('hello'); // ^? any


L'activation de l'option de compilateur noImplicitAny amènera le compilateur à mettre en surbrillance tous les endroits où le type d'une variable est automatiquement déduit comme any . Dans notre exemple, TypeScript nous demandera de spécifier le type de l'argument de la fonction.


 function parse(str) { // ^ Error: Parameter 'str' implicitly has an 'any' type. return str.split(''); }


Lorsque nous spécifions le type, TypeScript détectera rapidement l'erreur de transmission d'un nombre à un paramètre de chaîne. La valeur de retour de la fonction, stockée dans la variable res2 , aura également le type correct.


 function parse(str: string) { return str.split(''); } const res1 = parse(42); // ^ Error: Argument of type 'number' is not // assignable to parameter of type 'string' const res2 = parse('hello'); // ^? string[]


Type inconnu dans les variables de capture

 { "compilerOptions": { "useUnknownInCatchVariables": true } }


La configuration de useUnknownInCatchVariables permet une gestion sûre des exceptions dans les blocs try-catch. Par défaut, TypeScript suppose que le type d'erreur dans un bloc catch est any , ce qui nous permet de faire n'importe quoi avec l'erreur.


Par exemple, nous pourrions transmettre l'erreur interceptée telle quelle à une fonction de journalisation qui accepte une instance de Error .


 function logError(err: Error) { // ... } try { return JSON.parse(userInput); } catch (err) { // ^? any logError(err); }


Cependant, en réalité, il n'y a aucune garantie sur le type d'erreur, et nous ne pouvons déterminer son vrai type qu'au moment de l'exécution lorsque l'erreur se produit. Si la fonction de journalisation reçoit quelque chose qui n'est pas une Error , cela entraînera une erreur d'exécution.


Par conséquent, l'option useUnknownInCatchVariables fait passer le type d'erreur de any à unknown pour nous rappeler de vérifier le type d'erreur avant de faire quoi que ce soit avec.


 try { return JSON.parse(userInput); } catch (err) { // ^? unknown // Now we need to check the type of the value if (err instanceof Error) { logError(err); } else { logError(new Error('Unknown Error')); } }


Maintenant, TypeScript nous demandera de vérifier le type de la variable err avant de la transmettre à la fonction logError , ce qui donnera un code plus correct et plus sûr. Malheureusement, cette option ne résout pas les erreurs de frappe dans les fonctions promise.catch() ou les fonctions de rappel.


Mais nous discuterons des moyens de faire face à any tels cas dans le prochain article.

Vérification de type pour les méthodes Call et Apply

 { "compilerOptions": { "strictBindCallApply": true } }


Une autre option corrige l'apparence de any les appels en fonction via call and apply . C'est un cas moins courant que les deux premiers, mais il est toujours important de le considérer. Par défaut, TypeScript ne vérifie pas du tout les types dans de telles constructions.


Par exemple, nous pouvons passer n'importe quoi comme argument à une fonction, et à la fin, nous recevrons toujours le type any .


 function parse(value: string) { return parseInt(value, 10); } const n1 = parse.call(undefined, '10'); // ^? any const n2 = parse.call(undefined, false); // ^? any


L'activation de l'option strictBindCallApply rend TypeScript plus intelligent, de sorte que le type de retour sera correctement déduit en tant que number . Et lorsque vous essayez de passer un argument du mauvais type, TypeScript pointera vers l'erreur.


 function parse(value: string) { return parseInt(value, 10); } const n1 = parse.call(undefined, '10'); // ^? number const n2 = parse.call(undefined, false); // ^ Argument of type 'boolean' is not // assignable to parameter of type 'string'.


Types stricts pour le contexte d'exécution

 { "compilerOptions": { "noImplicitThis": true } }


L'option suivante qui peut aider à empêcher l'apparition de any dans votre projet corrige la gestion du contexte d'exécution dans les appels de fonction. La nature dynamique de JavaScript rend difficile la détermination statique du type de contexte à l'intérieur d'une fonction.


Par défaut, TypeScript utilise le type any pour le contexte dans de tels cas et ne fournit aucun avertissement.


 class Person { private name: string; constructor(name: string) { this.name = name; } getName() { return function () { return this.name; // ^ 'this' implicitly has type 'any' because // it does not have a type annotation. }; } }


L'activation de l'option de compilateur noImplicitThis nous invitera à spécifier explicitement le type de contexte pour une fonction. De cette façon, dans l'exemple ci-dessus, nous pouvons détecter l'erreur d'accès au contexte de la fonction au lieu du champ name de la classe Person .


Prise en charge nulle et indéfinie dans TypeScript

 { "compilerOptions": { "strictNullChecks": true } }


Ensuite, plusieurs options incluses dans le mode strict n'entraînent pas l'apparition de any type dans la base de code. Cependant, ils rendent le comportement du compilateur TS plus strict et permettent de trouver plus d'erreurs lors du développement.


La première de ces options corrige la gestion de null et undefined dans TypeScript. Par défaut, TypeScript suppose que null et undefined sont des valeurs valides pour n'importe quel type, ce qui peut entraîner des erreurs d'exécution inattendues.


L'activation de l'option de compilateur strictNullChecks oblige le développeur à gérer explicitement les cas où null et undefined peuvent se produire.


Par exemple, considérez le code suivant :


 const users = [ { name: 'Oby', age: 12 }, { name: 'Heera', age: 32 }, ]; const loggedInUser = users.find(u => u.name === 'Max'); // ^? { name: string; age: number; } console.log(loggedInUser.age); // ^ TypeError: Cannot read properties of undefined


Ce code se compilera sans erreur, mais il peut générer une erreur d'exécution si l'utilisateur portant le nom "Max" n'existe pas dans le système et que users.find() renvoie undefined . Pour éviter cela, nous pouvons activer l'option de compilateur strictNullChecks .


Maintenant, TypeScript nous forcera à gérer explicitement la possibilité que null ou undefined soit renvoyé par users.find() .


 const loggedInUser = users.find(u => u.name === 'Max'); // ^? { name: string; age: number; } | undefined if (loggedInUser) { console.log(loggedInUser.age); }


En gérant explicitement la possibilité de null et undefiined , nous pouvons éviter les erreurs d'exécution et nous assurer que notre code est plus robuste et sans erreur.

Types de fonctions strictes

 { "compilerOptions": { "strictFunctionTypes": true } }


L'activation strictFunctionTypes rend le compilateur de TypeScript plus intelligent. Avant la version 2.6, TypeScript ne vérifiait pas la contravariance des arguments de fonction. Cela conduira à des erreurs d'exécution si la fonction est appelée avec un argument du mauvais type.


Par exemple, même si un type de fonction est capable de gérer à la fois des chaînes et des nombres, nous pouvons affecter une fonction à ce type qui ne peut gérer que des chaînes. Nous pouvons toujours passer un nombre à cette fonction, mais nous recevrons une erreur d'exécution.


 function greet(x: string) { console.log("Hello, " + x.toLowerCase()); } type StringOrNumberFn = (y: string | number) => void; // Incorrect Assignment const func: StringOrNumberFn = greet; // TypeError: x.toLowerCase is not a function func(10);


Heureusement, l'activation de l'option strictFunctionTypes corrige ce comportement, et le compilateur peut détecter ces erreurs au moment de la compilation, nous montrant un message détaillé de l'incompatibilité de type dans les fonctions.


 const func: StringOrNumberFn = greet; // ^ Type '(x: string) => void' is not assignable to type 'StringOrNumberFn'. // Types of parameters 'x' and 'y' are incompatible. // Type 'string | number' is not assignable to type 'string'. // Type 'number' is not assignable to type 'string'.


Initialisation de la propriété de classe

 { "compilerOptions": { "strictPropertyInitialization": true } }


Enfin, l'option strictPropertyInitialization permet de vérifier l'initialisation obligatoire des propriétés de classe pour les types qui n'incluent pas undefined comme valeur.


Par exemple, dans le code suivant, le développeur a oublié d'initialiser la propriété email . Par défaut, TypeScript ne détecterait pas cette erreur et un problème pourrait survenir lors de l'exécution.


 class UserAccount { name: string; email: string; constructor(name: string) { this.name = name; // Forgot to assign a value to this.email } }


Cependant, lorsque l'option strictPropertyInitialization est activée, TypeScript mettra en évidence ce problème pour nous.


 email: string; // ^ Error: Property 'email' has no initializer and // is not definitely assigned in the constructor.

Signatures d'index sécurisées

 { "compilerOptions": { "noUncheckedIndexedAccess": true } }


L'option noUncheckedIndexedAccess ne fait pas partie du mode strict , mais c'est une autre option qui peut aider à améliorer la qualité du code dans votre projet. Il permet de vérifier que les expressions d'accès à l'index ont un type de retour null ou undefined , ce qui peut éviter les erreurs d'exécution.


Considérez l'exemple suivant, où nous avons un objet pour stocker les valeurs mises en cache. Nous obtenons alors la valeur de l'une des clés. Bien sûr, nous n'avons aucune garantie que la valeur de la clé souhaitée existe réellement dans le cache.


Par défaut, TypeScript supposerait que la valeur existe et a le type string . Cela peut entraîner une erreur d'exécution.


 const cache: Record<string, string> = {}; const value = cache['key']; // ^? string console.log(value.toUpperCase()); // ^ TypeError: Cannot read properties of undefined


L'activation de l'option noUncheckedIndexedAccess dans TypeScript nécessite de vérifier les expressions d'accès à l'index pour le type de retour undefined , ce qui peut nous aider à éviter les erreurs d'exécution. Cela s'applique également à l'accès aux éléments d'un tableau.


 const cache: Record<string, string> = {}; const value = cache['key']; // ^? string | undefined if (value) { console.log(value.toUpperCase()); }

Configuration recommandée

En fonction des options décrites, il est fortement recommandé d'activer les options strict et noUncheckedIndexedAccess dans le fichier tsconfig.json de votre projet pour une sécurité de type optimale.


 { "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true, } }


Si vous avez déjà activé l'option strict , vous pouvez envisager de supprimer les options suivantes pour éviter de dupliquer l'option strict: true :


  • noImplicitAny
  • useUnknownInCatchVariables
  • strictBindCallApply
  • noImplicitThis
  • strictFunctionTypes
  • strictNullChecks
  • strictPropertyInitialization


Il est également recommandé de supprimer les options suivantes qui peuvent affaiblir le système de type ou provoquer des erreurs d'exécution :


  • keyofStringsOnly
  • noStrictGenericChecks
  • suppressImplicitAnyIndexErrors
  • suppressExcessPropertyErrors


En examinant et en configurant soigneusement ces options, vous pouvez obtenir une sécurité de type optimale et une meilleure expérience de développement dans vos projets TypeScript.

Conclusion

TypeScript a parcouru un long chemin dans son évolution, améliorant constamment son compilateur et son système de typage. Cependant, pour maintenir la rétrocompatibilité, la configuration de TypeScript est devenue plus complexe, avec de nombreuses options qui peuvent affecter considérablement la qualité de la vérification de type.


En examinant et en configurant soigneusement ces options, vous pouvez obtenir une sécurité de type optimale et une meilleure expérience de développement dans vos projets TypeScript. Il est important de savoir quelles options activer et supprimer d'une configuration de projet.


Comprendre les conséquences de la désactivation de certaines options vous permettra de prendre des décisions éclairées pour chacune d'entre elles.


Il est important de garder à l'esprit qu'un typage strict peut avoir des conséquences. Pour gérer efficacement la nature dynamique de JavaScript, vous devrez avoir une bonne compréhension de TypeScript au-delà de la simple spécification de "nombre" ou "chaîne" après une variable.


Vous devrez vous familiariser avec des constructions plus complexes et l'écosystème de bibliothèques et d'outils TypeScript-first pour résoudre plus efficacement les problèmes liés au type qui se poseront.


Par conséquent, écrire du code peut nécessiter un peu plus d'efforts, mais d'après mon expérience, cet effort en vaut la peine pour les projets à long terme.


J'espère que vous avez appris quelque chose de nouveau grâce à cet article. Ceci est la première partie d'une série. Dans le prochain article, nous verrons comment améliorer la sécurité des types et la qualité du code en améliorant les types de la bibliothèque standard de TypeScript. Restez à l'écoute et merci d'avoir lu !

Liens utiles