Rendre TypeScript vraiment « fortement typé » | de Maksim Zemskov | septembre 2023

[ad_1]
TypeScript prétend être un langage de programmation fortement typé construit sur JavaScript, offrant de meilleurs outils à n’importe quelle échelle. Cependant, TypeScript inclut le any
type, qui peut souvent se faufiler implicitement dans une base de code et entraîner la perte de nombreux avantages de TypeScript.
Cet article explore les moyens de prendre le contrôle du any
tapez dans des projets TypeScript. Préparez-vous à libérer la puissance de TypeScript, pour atteindre une sécurité de type ultime et améliorer la qualité du code.
TypeScript fournit une gamme d’outils supplémentaires pour améliorer l’expérience et la productivité des développeurs :
- Cela permet de détecter les erreurs dès le début de la phase de développement.
- Il offre une excellente saisie semi-automatique pour les éditeurs de code et les IDE.
- Il permet une refactorisation facile de grandes bases de code grâce à de fantastiques outils de navigation de code et à une refactorisation automatique.
- Il simplifie la compréhension d’une base de code en fournissant une sémantique supplémentaire et des structures de données explicites via des types.
Cependant, dès que vous commencez à utiliser le any
tapez votre base de code, vous perdez tous les avantages ci-dessus. Le any
type est une faille dangereuse dans le système de types, et son utilisation désactive toutes les capacités de vérification de type et tous les outils qui dépendent de la vérification de type. En conséquence, tous les avantages de TypeScript sont perdus : des bugs sont manqués, les éditeurs de code deviennent moins utiles, et bien plus encore.
Par exemple, considérons l’exemple suivant :
function parse(data: any) {
return data.split('');
}// Case 1
const res1 = parse(42);
// ^ TypeError: data.split is not a function
// Case 2
const res2 = parse('hello');
// ^ any
Dans le code ci-dessus :
- Vous manquerez la saisie semi-automatique à l’intérieur du
parse
fonction. Quand tu tapesdata.
dans votre éditeur, vous ne recevrez pas de suggestions correctes sur les méthodes disponibles pourdata
. - Dans le premier cas, il existe un
TypeError: data.split is not a function
erreur car nous avons passé un nombre au lieu d’une chaîne. TypeScript n’est pas en mesure de mettre en évidence l’erreur carany
désactive la vérification du type. - Dans le deuxième cas, le
res2
La variable a également leany
taper. Cela signifie qu’une seule utilisation deany
peut avoir un effet en cascade sur une grande partie d’une base de code.
En utilisant any
n’est acceptable que dans des cas extrêmes ou pour des besoins de prototypage. En général, il vaut mieux éviter d’utiliser any
pour tirer le meilleur parti de TypeScript.
Il est important de connaître les sources du any
tapez une base de code car vous écrivez explicitement any
n’est pas la seule option. Malgré tous nos efforts pour éviter d’utiliser le any
tapez, il peut parfois se faufiler implicitement dans une base de code.
Il existe quatre sources principales de any
tapez une base de code :
- Options du compilateur dans tsconfig.
- La bibliothèque standard de TypeScript.
- Dépendances du projet.
- Utilisation explicite de
any
dans une base de code.
J’ai déjà écrit des articles sur Considérations clés dans tsconfig et Amélioration des types de bibliothèques standard pour les deux premiers points. Veuillez les consulter pour améliorer la sécurité des caractères dans vos projets.
Cette fois, nous nous concentrerons sur les outils automatiques de contrôle de l’apparence du any
tapez une base de code.
ESLint est un outil d’analyse statique populaire que les développeurs Web utilisent pour garantir les meilleures pratiques et le formatage du code. Il peut appliquer des styles de codage et trouver du code qui ne respecte pas certaines directives.
ESLint peut également être utilisé avec des projets TypeScript, grâce à typesctipt-eslint brancher. Très probablement, ce plugin a déjà été installé dans votre projet. Mais sinon, vous pouvez suivre le fonctionnaire Guide de Démarrage.
La configuration la plus courante pour typescript-eslint
est comme suit:
module.exports = {
extends: (
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
),
plugins: ('@typescript-eslint'),
parser: '@typescript-eslint/parser',
root: true,
};
Cette configuration permet eslint
pour comprendre TypeScript au niveau de la syntaxe, vous permettant d’écrire des règles ESlint simples qui s’appliquent aux types écrits manuellement dans un code. Par exemple, vous pouvez interdire l’utilisation explicite de any
.
Le recommended
Le préréglage contient un ensemble soigneusement sélectionné de règles ESLint pour améliorer l’exactitude du code. Bien qu’il soit recommandé d’utiliser l’intégralité du préréglage, pour cet article, nous nous concentrerons uniquement sur le no-explicit-any
règle.
Le mode strict de TypeScript empêche l’utilisation de any
mais ça n’empêche pas any
d’être explicitement utilisé. Le no-explicit-any
la règle permet d’interdire l’écriture manuelle any
n’importe où dans une base de code.
// ❌ Incorrect
function loadPokemons(): any {}
// ✅ Correct
function loadPokemons(): unknown {}// ❌ Incorrect
function parsePokemons(data: Response<any>): Array<Pokemon> {}
// ✅ Correct
function parsePokemons(data: Response<unknown>): Array<Pokemon> {}
// ❌ Incorrect
function reverse<T extends Array<any>>(array: T): T {}
// ✅ Correct
function reverse<T extends Array<unknown>>(array: T): T {}
L’objectif principal de cette règle est d’empêcher l’utilisation de any
dans toute l’équipe. C’est un moyen de renforcer l’accord de l’équipe sur le fait que l’utilisation de any
dans le projet est déconseillé.
Il s’agit d’un objectif crucial car même une seule utilisation de any
peut avoir un impact en cascade sur une partie importante de la base de code en raison de inférence de type. Cependant, cela est encore loin d’atteindre la sécurité de type ultime.
Bien que nous ayons traité explicitement des any
il y en a encore beaucoup d’implicites any
dans les dépendances d’un projet, y compris les packages npm et la bibliothèque standard de TypeScript.
Considérez le code suivant, que l’on retrouve probablement dans n’importe quel projet :
const response = await fetch('<https://pokeapi.co/api/v2/pokemon>');
const pokemons = await response.json();
// ^? anyconst settings = JSON.parse(localStorage.getItem('user-settings'));
// ^? any
Les deux variables pokemons
et settings
ont été implicitement donnés any
taper. Ni l’un ni l’autre no-explicit-any
ni le mode strict de TypeScript ne nous avertiront dans ce cas. Pas encore.
Cela se produit parce que les types pour response.json()
et JSON.parse()
proviennent de la bibliothèque standard de TypeScript, où ces méthodes ont un any
annotation. Nous pouvons toujours spécifier manuellement un meilleur type pour nos variables, mais il existe près de 1 200 occurrences de any
dans la bibliothèque standard. Il est presque impossible de se souvenir de tous les cas où any
peut se faufiler dans notre base de code à partir de la bibliothèque standard.
Il en va de même pour les dépendances externes. Il existe de nombreuses bibliothèques mal typées dans npm, la plupart étant encore écrites en JavaScript. En conséquence, l’utilisation de telles bibliothèques peut facilement conduire à de nombreux problèmes implicites. any
dans une base de code.
D’une manière générale, il existe encore de nombreuses façons de any
pour se faufiler dans notre code.
Idéalement, nous aimerions un paramètre dans TypeScript qui oblige le compilateur à se plaindre de toute variable ayant reçu le any
tapez pour quelque raison que ce soit. Malheureusement, un tel paramètre n’existe pas actuellement et ne devrait pas être ajouté.
Nous pouvons obtenir ce comportement en utilisant le mode de vérification de type du typescript-eslint
brancher. Ce mode fonctionne avec TypeScript pour fournir des informations de type complètes du compilateur TypeScript aux règles ESLint. Avec ces informations, il est possible d’écrire des règles ESLint plus complexes qui étendent les capacités de vérification de type de TypeScript. Par exemple, une règle peut rechercher toutes les variables portant le any
tapez, quelle que soit la façon dont any
a été obtenu.
Pour utiliser des règles sensibles au type, vous devez ajuster légèrement la configuration ESLint. Ce code peut aider :
module.exports = {
extends: (
'eslint:recommended',
- 'plugin:@typescript-eslint/recommended',
+ 'plugin:@typescript-eslint/recommended-type-checked',
),
plugins: ('@typescript-eslint'),
parser: '@typescript-eslint/parser',
+ parserOptions: {
+ project: true,
+ tsconfigRootDir: __dirname,
+ },
root: true,
};
Pour activer l’inférence de type pour typescript-eslint
ajouter parserOptions
à la configuration ESLint. Ensuite, remplacez le recommended
préréglé avec recommended-type-checked
. Ce dernier préréglage ajoute environ 17 nouvelles règles puissantes. Pour cet article, nous nous concentrerons sur cinq d’entre eux seulement.
Le no-unsafe-argument
la règle recherche les appels de fonction dans lesquels une variable de type any
est passé en paramètre. Lorsque cela se produit, la vérification du type et tous les avantages d’un typage fort sont perdus.
Par exemple, considérons un saveForm
fonction qui nécessite un objet comme paramètre. Supposons que nous recevions JSON, l’analysions et obtenions un any
taper.
// ❌ Incorrectfunction saveForm(values: FormValues) {
console.log(values);
}
const formValues = JSON.parse(userInput);
// ^? any
saveForm(formValues);
// ^ Unsafe argument of type `any` assigned
// to a parameter of type `FormValues`.
Quand nous appelons le saveForm
fonctionner avec ce paramètre, le no-unsafe-argument
la règle le signale comme dangereux et nous oblige à spécifier le type approprié pour le value
variable.
Cette règle est suffisamment puissante pour inspecter en profondeur les structures de données imbriquées dans les arguments de fonction. Par conséquent, vous pouvez être sûr que le passage d’objets en tant qu’arguments de fonction ne contiendra jamais de données non typées.
// ❌ IncorrectsaveForm({
name: 'John',
address: JSON.parse(addressJson),
// ^ Unsafe assignment of an `any` value.
});
La meilleure façon de corriger l’erreur est d’utiliser TypeScript type de rétrécissement ou une bibliothèque de validation telle que Zod ou Superstructure. Par exemple, écrivons le parseFormValues
fonction qui restreint le type précis de données analysées.
// ✅ Correctfunction parseFormValues(data: unknown): FormValues {
if (
typeof data === 'object' &&
data !== null &&
'name' in data &&
typeof data('name') === 'string' &&
'address' in data &&
typeof data.address === 'string'
) {
const { name, address } = data;
return { name, address };
}
throw new Error('Failed to parse form values');
}
const formValues = parseFormValues(JSON.parse(userInput));
// ^? FormValues
saveForm(formValues);
Notez qu’il est permis de passer le any
tapez comme argument à une fonction qui accepte unknown
car cela ne pose aucun problème de sécurité.
L’écriture de fonctions de validation de données peut s’avérer fastidieuse, en particulier lorsqu’il s’agit de grandes quantités de données. Par conséquent, il vaut la peine d’envisager l’utilisation d’une bibliothèque de validation de données. Par exemple, avec Zod, le code ressemblerait à ceci :
// ✅ Correctimport { z } from 'zod';
const schema = z.object({
name: z.string(),
address: z.string(),
});
const formValues = schema.parse(JSON.parse(userInput));
// ^? { name: string, address: string }
saveForm(formValues);
Le no-unsafe-assignment
La règle recherche les affectations de variables dans lesquelles une valeur a le any
taper. De telles affectations peuvent induire le compilateur en erreur en lui faisant croire qu’une variable a un certain type alors que les données peuvent avoir un type différent.
Prenons l’exemple précédent d’analyse JSON :
// ❌ Incorrectconst formValues = JSON.parse(userInput);
// ^ Unsafe assignment of an `any` value
Grace à no-unsafe-assignment
règle, nous pouvons attraper le any
tapez avant même de passer formValues
autre part. La stratégie de correction reste la même : nous pouvons utiliser le rétrécissement du type pour fournir un type spécifique à la valeur de la variable.
// ✅ Correctconst formValues = parseFormValues(JSON.parse(userInput));
// ^? FormValues
Ces deux règles se déclenchent beaucoup moins fréquemment. Cependant, d’après mon expérience, ils sont très utiles lorsque vous essayez d’utiliser des dépendances tierces mal typées.
Le no-unsafe-member-access
La règle nous empêche d’accéder aux propriétés d’un objet si une variable a le any
tapez car c’est peut-être null
ou undefined
.
Le no-unsafe-call
la règle nous empêche d’appeler une variable avec le any
tapez en tant que fonction, car ce n’est peut-être pas une fonction.
Imaginons que nous ayons une bibliothèque tierce mal typée appelée untyped-auth
:
// ❌ Incorrectimport { authenticate } from 'untyped-auth';
// ^? any
const userInfo = authenticate();
// ^? any ^ Unsafe call of an `any` typed value.
console.log(userInfo.name);
// ^ Unsafe member access .name on an `any` value.
Le linter met en évidence deux problèmes :
- Appeler le
authenticate
la fonction peut être dangereuse, car nous pouvons oublier de lui transmettre des arguments importants. - Lire le
name
propriété de lauserInfo
l’objet est dangereux, comme il le seranull
si l’authentification échoue.
La meilleure façon de corriger ces erreurs est d’envisager d’utiliser une bibliothèque avec une API fortement typée. Mais si ce n’est pas une option, vous pouvez augmenter les types de bibliothèques toi-même. Un exemple avec les types de bibliothèques fixes ressemblerait à ceci :
// ✅ Correctimport { authenticate } from 'untyped-auth';
// ^? (login: string, password: string) => Promise<UserInfo | null>
const userInfo = await authenticate('test', 'pwd');
// ^? UserInfo | null
if (userInfo) {
console.log(userInfo.name);
}
Le no-unsafe-return
la règle aide à ne pas retourner accidentellement le any
tapez à partir d’une fonction qui devrait renvoyer quelque chose de plus spécifique. De tels cas peuvent induire le compilateur en erreur en lui faisant croire qu’une valeur renvoyée a un certain type, alors que les données peuvent en réalité avoir un type différent.
Par exemple, supposons que nous ayons une fonction qui analyse JSON et renvoie un objet avec deux propriétés.
// ❌ Incorrectinterface FormValues {
name: string;
address: string;
}
function parseForm(json: string): FormValues {
return JSON.parse(json);
// ^ Unsafe return of an `any` typed value.
}
const form = parseForm('null');
console.log(form.name);
// ^ TypeError: Cannot read properties of null
Le parseForm
La fonction peut entraîner des erreurs d’exécution dans n’importe quelle partie du programme où elle est utilisée puisque la valeur analysée n’est pas vérifiée. Le no-unsafe-return
La règle empêche de tels problèmes d’exécution.
Il est facile de résoudre ce problème en ajoutant une validation pour garantir que le JSON analysé correspond au type attendu. Utilisons la bibliothèque Zod cette fois :
// ✅ Correctimport { z } from 'zod';
const schema = z.object({
name: z.string(),
address: z.string(),
});
function parseForm(json: string): FormValues {
return schema.parse(JSON.parse(json));
}
L’utilisation de règles de vérification de type crée une pénalité de performances pour ESLint car il doit appeler le compilateur TypeScript pour déduire tous les types. Ce ralentissement est principalement perceptible lors de l’exécution du linter dans des hooks de pré-commit et dans CI, mais il n’est pas perceptible lorsque vous travaillez dans un IDE. La vérification du type est effectuée une fois au démarrage de l’IDE, puis met à jour les types à mesure que vous modifiez le code.
Il convient de noter que le simple fait de déduire les types fonctionne plus rapidement que l’invocation habituelle du tsc
compilateur. Par exemple, sur notre projet le plus récent avec environ 1,5 million de lignes de code TypeScript, tapez la vérification via tsc
prend environ 11 minutes, tandis que le temps supplémentaire requis pour le démarrage des règles sensibles au type d’ESLint n’est que d’environ deux minutes.
La sécurité supplémentaire fournie par l’utilisation de règles d’analyse statique sensibles au type vaut le compromis pour notre équipe. Sur les petits projets, cette décision est encore plus facile à prendre.
Contrôler l’utilisation de any
dans les projets TypeScript est crucial pour obtenir une sécurité de type et une qualité de code optimales. En utilisant le typescript-eslint
plugin, les développeurs peuvent identifier et éliminer toute occurrence du any
tapez leur base de code, ce qui donne une base de code plus robuste et plus maintenable.
En utilisant des règles ESlint sensibles au type, toute apparition du mot-clé any
dans notre base de code sera une décision délibérée plutôt qu’une erreur ou un oubli. Cette approche nous évite d’utiliser any
dans notre propre code, ainsi que dans la bibliothèque standard et les dépendances tierces.
Dans l’ensemble, un linter sensible au type nous permet d’atteindre un niveau de sécurité de type similaire à celui des langages de programmation typés statiquement tels que Java, Go, Rust et autres. Cela simplifie grandement le développement et la maintenance de grands projets.
J’espère que vous avez appris quelque chose de nouveau grâce à cet article. Merci pour la lecture!
[ad_2]
Source link