Les 5 commandements du développeur TypeScript

image


De plus en plus de projets et d'équipes utilisent TypeScript. Cependant, appliquer simplement TypeScript et en tirer le maximum est des choses très différentes.


Je vous présente une liste des meilleures pratiques de haut niveau pour l'utilisation de TypeScript qui vous aideront à tirer le meilleur parti de ce langage.


Ne mens pas


Les types sont un contrat. Qu'est-ce que cela signifie? Lorsque vous implémentez une fonction, son type devient une promesse pour les autres développeurs (ou pour vous-même à l'avenir!) Que, lorsqu'elle est appelée, cette fonction retourne un certain type de valeur.


Dans l'exemple suivant, le type de la fonction getUser garantit qu'elle renvoie un objet qui a toujours deux propriétés: name et age .


 interface User { name: string; age: number; } function getUser(id: number): User { /* ... */ } 

TypeScript est un langage très flexible. Il comporte de nombreux compromis destinés à faciliter l'introduction de la langue. Par exemple, il vous permet d'implémenter la fonction getUser comme ceci:


 function getUser(id: number): User { return { age: 12 } as User; } 

Ne fais pas ça! C'est un FAUX. En créant un tel code, vous LIEZ à d'autres développeurs (qui utiliseront votre fonction dans leurs fonctions). Ils s'attendent à ce que l'objet renvoyé par getUser toujours une sorte de champ de name . Mais il n'est pas là! De plus, que se passe-t-il lorsque votre collègue écrit getUser(1).name.toString() ? Tu sais très bien que ...


Ici, bien sûr, le mensonge semble évident. Cependant, en travaillant avec une grande base de code, vous vous retrouverez souvent dans des situations où la valeur que vous souhaitez renvoyer (ou transmettre) correspond presque au type attendu. Il faut du temps et des efforts pour trouver la cause de la discordance de type , et vous êtes pressé ... alors vous décidez d'utiliser le casting de type.


Cependant, ce faisant, vous rompez le contrat sacré . Il est TOUJOURS préférable de prendre le temps et de comprendre pourquoi les types ne correspondent pas que d'utiliser la conversion de type. Il est très probable qu'un bogue d'exécution se cache sous la surface.


Ne mentez pas. Respectez vos contrats.


Soyez précis


Les types sont de la documentation. Lorsque vous documentez une fonction, ne souhaitez-vous pas transmettre autant d'informations que possible?


 //   function getUser(id) { /* ... */ } //     : name  age function getUser(id) { /* ... */ } //  id       id , //     : name  age. //     undefined. function getUser(id) { /* ... */ } 

Quel commentaire pour la fonction getUser vous plus? Plus vous savez qu'une fonction revient, mieux c'est. Par exemple, sachant qu'il peut renvoyer undefined , vous pouvez écrire un bloc if pour vérifier si l'objet renvoyé par la fonction est défini avant de demander les propriétés de cet objet.


Exactement la même chose avec les types: plus un type est décrit avec précision, plus il transmet d'informations.


 function getUserType(id: number): string { /* ... */ } function getUserType(id: number): 'standard' | 'premium' | 'admin' { /* ... */ } 

La deuxième version de la fonction getUserType beaucoup plus informative, et donc l'appelant est dans une situation beaucoup plus pratique. Il est plus facile de traiter la valeur si vous savez probablement (contrats, rappelez-vous?) Que ce sera l'une des trois lignes données, et pas n'importe quelle ligne. Pour commencer avec ce que vous savez avec certitude - une valeur ne peut pas être une chaîne vide.


Prenons un exemple plus réel. Le type d' State décrit l'état du composant qui demande des données au serveur principal. Ce type est-il précis?


 interface State { isLoading: boolean; data?: string[]; errorMessage?: string; } 

Un client utilisant ce type doit gérer une combinaison improbable de valeurs de propriété d'état. Par exemple, une situation est impossible lorsque les propriétés data et errorMessage sont définies simultanément: une demande de données peut réussir ou échouer.


Nous pouvons rendre le type beaucoup plus précis à l'aide de types d'unions discriminés :


 type State = | { status: 'loading' } | { status: 'successful', data: string[] } | { status: 'failed', errorMessage: string }; 

Le client utilisant ce type dispose désormais de beaucoup plus d'informations: il n'a plus besoin de traiter des combinaisons de propriétés incorrectes.


Soyez précis. Passez autant d'informations que possible sur vos types.


Commencez avec les types


Comme les types sont à la fois un contrat et une documentation, ils sont parfaits pour concevoir vos fonctions (ou méthodes).


Il existe de nombreux articles sur Internet qui conseillent aux programmeurs de réfléchir avant d'écrire du code . Je partage pleinement cette approche. La tentation de passer directement au code est grande, mais cela conduit souvent à de mauvaises décisions. Un peu de temps passé à réfléchir à la mise en œuvre est toujours payant.


Les types sont extrêmement utiles dans ce processus. La réflexion conduit à la création de signatures de types de fonctions liées à la résolution de votre problème. Et c'est génial, parce que vous vous concentrez sur ce que font vos fonctions, au lieu de penser à la façon dont elles le font.


React JS a le concept de composants d'ordre supérieur (HOC). Ce sont des fonctions qui étendent le composant donné d'une manière ou d'une autre. Par exemple, vous pouvez créer un composant d'ordre supérieur withLoadingIndicator qui ajoute un indicateur de chargement à un composant existant.


Écrivons une signature de type pour cette fonction. La fonction accepte une entrée de composant et renvoie également un composant. Pour représenter un composant, nous pouvons utiliser le type React ComponentType .


ComponentType est un type générique qui est paramétré par le type de propriétés de composant. withLoadingIndicator accepte un composant et renvoie un nouveau composant qui affiche le composant d'origine ou l'indicateur de chargement. La décision sur les éléments à afficher est prise en fonction de la valeur de la nouvelle propriété logique - isLoading . Ainsi, le composant renvoyé a besoin des mêmes propriétés que celui d'origine, seule la nouvelle propriété isLoading est ajoutée.


Nous finaliserons le type. withLoadingIndicator accepte un composant de type ComponentType<P> , où P désigne le type de propriété. withLoadingIndicator renvoie un composant avec des propriétés avancées de type P & { isLoading: boolean } .


 const withLoadingIndicator = <P>(Component: ComponentType<P>) : ComponentType<P & { isLoading: boolean }> => ({ isLoading, ...props }) => { /* ... */ } 

Concernant les types de fonctions, nous avons été obligés de penser à ce qui sera sur son entrée et ce qui sera sur la sortie. En d'autres termes, nous avons dû concevoir une fonction . Écrire son implémentation maintenant est facile.


Commencez par les types. Laissez les types vous forcer à concevoir en premier, puis écrivez ensuite l'implémentation.


Faites preuve de rigueur


Les trois premiers commandements vous obligent à porter une attention particulière aux types. Heureusement, lorsque vous résolvez ce problème, vous n'avez pas à tout faire vous-même - le compilateur TypeScript vous informe souvent lorsque vos types mentent ou lorsqu'ils ne sont pas suffisamment précis.


Vous pouvez aider le compilateur à faire encore mieux en incluant l'indicateur --strict . Il s'agit d'un indicateur de méta qui --noImplicitAny toutes les options de vérification de type strictes: --noImplicitAny , --noImplicitThis , --alwaysStrict , --strictBindCallApply , --strictNullChecks , --strictFunctionTypes et --strictPropertyInitialization .


Que font les drapeaux? D'une manière générale, leur inclusion entraîne une augmentation du nombre d'erreurs de compilation TypeScript. Et c'est bien! Plus d'erreurs de compilation - plus d'aide du compilateur.


Voyons comment l'activation de l'indicateur --strictNullChecks aide à détecter un faux dans le code.


 function getUser(id: number): User { if (id >= 0) { return { name: 'John', age: 12 }; } else { return undefined; } } 

Le type getUser garantit que la fonction renvoie toujours un objet de type User . Cependant, regardez l'implémentation: une fonction peut également renvoyer undefined !


Heureusement, l'activation de l'indicateur --strictNullChecks entraîne une erreur de compilation:


 Type 'undefined' is not assignable to type 'User'. 

Le compilateur TypeScript détecte les faussetés. Pour se débarrasser de cette erreur, dites honnêtement toute la vérité:


 function getUser(id: number): User | undefined { /* ... */ } 

Acceptez la rigueur de la vérification de type. Laissez le compilateur vous protéger contre les erreurs.


Restez à jour


TypeScript se développe à un rythme très rapide. Une nouvelle version est publiée tous les deux mois. Chaque version apporte d'importantes améliorations linguistiques et de nouvelles fonctionnalités.


Il arrive souvent que les nouvelles fonctionnalités du langage vous permettent de définir des types plus précisément et de les vérifier plus strictement.


Par exemple, dans la version 2.0, des types d'unions discriminées ont été introduits (je les ai mentionnés dans le commandement Soyez précis ).


La version 3.2 a introduit l'indicateur du compilateur --strictBindCallApply , qui inclut le typage correct pour les fonctions de bind , d' call et d' apply .


La version 3.4 a amélioré l'inférence de type dans les fonctions d'ordre supérieur , ce qui a facilité l'utilisation de types exacts lors de l'écriture de code dans un style fonctionnel.


Ma position est qu'apprendre à connaître les fonctionnalités du langage introduites dans les versions récentes de TypeScript en vaut vraiment la peine. Cela peut souvent vous aider à suivre les quatre autres commandements de la liste.


Un bon point de départ est la feuille de route TypeScript officielle . Il sera également agréable de vérifier régulièrement la section TypeScript dans Microsoft Devblog , car toutes les annonces de version y sont publiées .


Restez à jour avec les nouvelles fonctionnalités de la langue et laissez cette connaissance travailler pour vous.


Résumé


J'espère que vous trouverez la liste utile. Comme toujours et en tout, il ne faut pas suivre aveuglément ces commandements. Mais je crois fermement que ces règles feront de vous un meilleur développeur TypeScript.


Je serai heureux de voir vos réflexions à ce sujet dans les commentaires.


Bonus


Avez-vous aimé cet article sur TypeScript? Je suis sûr que vous aimerez également ce PDF gratuit: 10 erreurs de développement TypeScript qui rendent votre code dangereux.

Source: https://habr.com/ru/post/fr461565/


All Articles