Malheureusement, nous devons admettre qu'en 2021, le flux est déjà nettement inférieur à TypeScript à la fois en popularité et en support par une variété d'utilitaires (et de bibliothèques), et il est temps de l'
Pourquoi avez-vous besoin de la sécurité de type en JavaScript?
JavaScript est un langage merveilleux. Non pas comme ça. L'écosystème construit autour de JavaScript est génial. Pour 2021, elle admire vraiment le fait que vous pouvez utiliser les fonctionnalités les plus modernes du langage, puis, en modifiant un paramètre du système de construction, transpilez le fichier exécutable afin de prendre en charge son exécution dans les anciennes versions de navigateurs, y compris IE8. , ce ne sera pas la nuit rappelez-vous Vous pouvez «écrire en HTML» (ce qui signifie JSX), puis en utilisant l'utilitaire
babel
(ou
tsc
) remplacer toutes les balises par des constructions JavaScript correctes comme appeler la bibliothèque React (ou toute autre, mais plus à ce sujet dans un autre article).
Pourquoi JavaScript est-il bon comme langage de script qui s'exécute dans votre navigateur?
- JavaScript n'a pas besoin d'être "compilé". Vous ajoutez simplement des constructions JavaScript et le navigateur doit les comprendre. Cela donne immédiatement un tas de choses pratiques et presque gratuites. Par exemple, déboguer directement dans le navigateur, qui n'est pas de la responsabilité du programmeur (qui ne doit pas oublier, par exemple, d'inclure un tas d'options de débogage du compilateur et des bibliothèques correspondantes), mais du développeur du navigateur. Vous n'avez pas besoin d'attendre 10 à 30 minutes (en temps réel pour C / C ++) pendant que votre projet de 10 000 lignes est compilé pour essayer d'écrire quelque chose différemment. Il vous suffit de changer la ligne, de recharger la page du navigateur et d'observer le nouveau comportement du code. Et dans le cas de l'utilisation, par exemple, de webpack, la page sera également rechargée pour vous. De nombreux navigateurs vous permettent de modifier le code directement dans la page en utilisant leurs outils de développement.
- - . 2021 . Chrome/Firefox, , , 5% (enterprise-) 30% (UI/) , .
- JavaScript , . — ( worker'). , 100% CPU ( UI ), , , Promise/async/await/etc.
- Dans le même temps, je ne considère même pas la question de savoir pourquoi JavaScript est important. Après tout, avec l'aide de JS, vous pouvez: valider les formulaires, mettre à jour le contenu de la page sans le recharger entièrement, ajouter des effets de comportement non standard, travailler avec de l'audio et de la vidéo, et vous pouvez même écrire l'intégralité du client de votre application d'entreprise dans JavaScript.
Comme avec presque n'importe quel langage de script (interprété), en JavaScript, vous pouvez ... écrire du code cassé. Si le navigateur n'atteint pas ce code, il n'y aura aucun message d'erreur, aucun avertissement, rien du tout. D'une part, c'est bien. Si votre site Web est volumineux et volumineux, même une erreur de syntaxe dans le code du gestionnaire de clic de bouton ne doit pas entraîner le chargement du site par l'utilisateur.
Mais, bien sûr, c'est mauvais. Parce que le fait même d'avoir quelque chose qui ne fonctionne pas quelque part sur le site est mauvais. Et ce serait génial, avant que le code n'atteigne un site de travail, de vérifier tous les scripts du site et de s'assurer qu'ils compilent au moins. Et idéalement - et travaillez. Pour cela, une variété d'ensembles d'utilitaires sont utilisés (mon ensemble préféré est npm + webpack + babel / tsc + karma + jsdom + mocha + chai).
Si nous vivons dans un monde idéal, tous les scripts de votre site, même ceux d'une seule ligne, sont couverts par des tests. Mais, malheureusement, le monde n'est pas idéal, et pour toute cette partie du code qui n'est pas couverte par les tests, nous ne pouvons compter que sur une sorte d'outils de vérification automatisés. Ce qui peut vérifier:
- JavaScript. , JavaScript, , , . /// .
- . , , . , :
var x = null; x.foo();
. — null .
En plus des erreurs de sémantique, il peut y avoir des erreurs encore plus terribles: des erreurs logiques. Lorsque le programme s'exécute sans erreurs, mais le résultat n'est pas du tout ce qui était attendu. Classique avec ajout de chaînes et de nombres:
console.log( input.value ) // 1
console.log( input.value + 1 ) // 11
Les outils d'analyse de code statique existants (eslint, par exemple) peuvent tenter de localiser un nombre significatif d'erreurs potentielles qu'un programmeur fait dans son code. Par example:
- Interdire une boucle for infinie avec une condition de terminaison de boucle incorrecte
- Interdiction des fonctions asynchrones comme arguments pour un constructeur de promesse
- Interdire les affectations dans les conditions
- et autres
Notez que toutes ces règles sont essentiellement des contraintes que le linter place sur le programmeur. Autrement dit, le linter réduit en fait les capacités du langage JavaScript afin que le programmeur fasse moins d'erreurs potentielles. Si vous activez les règles tout-tout, il sera alors impossible d'effectuer des affectations dans des conditions (bien que JavaScript le permette initialement), d'utiliser des clés en double dans les littéraux d'objet et même de ne pas être appelés
console.log()
.
L'ajout de types de variables et la vérification de type d'appels sont des limitations supplémentaires du langage JavaScript pour réduire les erreurs potentielles.
Essayer de multiplier un nombre par une chaîne
Une tentative d'accès à une propriété inexistante (non décrite dans le type) d'un objet.
Une tentative d'appeler une fonction avec un type d'argument incompatible.
Si nous écrivons ce code sans vérificateur de type, le code est transpilé avec succès. Aucun moyen d'analyse de code statique, s'il n'utilise (explicitement ou implicitement) des informations sur les types d'objets, ne pourra pas trouver ces erreurs.
Autrement dit, l'ajout de la saisie à JavaScript ajoute des restrictions supplémentaires au code que le programmeur écrit, mais cela vous permet de trouver des erreurs qui se produiraient autrement pendant l'exécution du script (c'est-à-dire, très probablement dans le navigateur de l'utilisateur).
Capacités de saisie JavaScript
| Couler | Manuscrit | |
|---|---|---|
| Possibilité de définir le type d'une variable, d'un argument ou d'un type de retour d'une fonction | |
|
| Capacité à décrire votre type d'objet (interface) | |
|
| Restriction des valeurs pour un type | |
|
| Extension de niveau de type distincte pour les énumérations |
|
|
| "Ajout de" types |
|
|
| "Types" supplémentaires pour les cas complexes |
|
|
Les deux moteurs pour la prise en charge du type JavaScript ont à peu près les mêmes capacités. Cependant, si vous venez de langages fortement typés, même le JavaScript typé présente une différence très importante par rapport à Java: tous les types décrivent essentiellement des interfaces, c'est-à-dire une liste de propriétés (et leurs types et / ou arguments). Et si deux interfaces décrivent les mêmes propriétés (ou compatibles), elles peuvent être utilisées à la place l'une de l'autre. Autrement dit, le code suivant est correct en JavaScript typé, mais clairement incorrect en Java, ou, par exemple, C ++:
type MyTypeA = { foo: string; bar: number; } type MyTypeB = { foo: string; } function myFunction( arg : MyTypeB ) : string { return `Hello, ${arg.foo}!`; } const myVar : MyTypeA = { foo: "World", bar: 42 } as MyTypeA; console.log( myFunction( myVar ) ); // "Hello, World!"
Ce code est correct du point de vue du JavaScript typé, puisque l'interface MyTypeB nécessite une propriété
foo
avec un type
string
, alors qu'une variable avec l'interface MyTypeA le fait.
Ce code peut être réécrit un peu plus court, en utilisant une interface littérale pour une variable
myVar
.
type MyTypeB = { foo: string; } function myFunction( arg : MyTypeB ) : string { return `Hello, ${arg.foo}!`; } const myVar = { foo: "World", bar: 42 }; console.log( myFunction( myVar ) ); // "Hello, World!"
Le type de variable
myVar
dans cet exemple est une interface littérale
{ foo: string, bar: number }
. Il est toujours compatible avec l'interface attendue d'un argument de
arg
fonction
myFunction
, donc ce code est sans erreur du point de vue, par exemple, de TypeScript.
Ce comportement réduit considérablement le nombre de problèmes lorsque vous travaillez avec différentes bibliothèques, du code personnalisé et même en appelant simplement des fonctions. Un exemple typique est quand une bibliothèque définit des options valides, et nous les passons en tant qu'objet d'options:
// - interface OptionsType { optionA?: string; optionB?: number; } export function libFunction( arg: number, options = {} as OptionsType) { /*...*/ }
// import {libFunction} from "lib"; libFunction( 42, { optionA: "someValue" } );
Notez que le type n'est
OptionsType
pas exporté de la bibliothèque (ni importé dans le code personnalisé). Mais cela ne vous empêche pas d'appeler la fonction en utilisant l'interface littérale pour le deuxième argument de la
options
fonction, et pour le système de typage - pour vérifier cet argument pour la compatibilité de type. Essayer de faire quelque chose comme ça en Java provoquera une confusion claire parmi le compilateur.
Comment ça marche du point de vue du navigateur?
Ni le TypeScript de Microsoft ni le flux de Facebook ne sont pris en charge par les navigateurs. De plus, les dernières extensions de langage JavaScript n'ont pas encore trouvé de support dans certains navigateurs. Alors, comment ce code est-il vérifié, d'une part, pour son exactitude, et d'autre part, comment est-il exécuté par le navigateur?
La réponse est traspilante. Tout code JavaScript "non standard" passe par un ensemble d'utilitaires qui transforment le code "non standard" (inconnu des navigateurs) en un ensemble d'instructions que les navigateurs comprennent. Et pour le typage, toute la «transformation» consiste dans le fait que tous les raffinements de type, toutes les descriptions d'interface, toutes les restrictions du code sont simplement supprimés. Par exemple, le code de l'exemple ci-dessus se transforme en ...
/* : type MyTypeA = { foo: string; bar: number; } */ /* : type MyTypeB = { foo: string; } */ function myFunction( arg /* : : MyTypeB */ ) /* : : string */ { return `Hello, ${arg.foo}!`; } const myVar /* : : MyTypeA */ = { foo: "World", bar: 42 } /* : as MyTypeA */; console.log( myFunction( myVar ) ); // "Hello, World!"
ceux.
function myFunction( arg ) { return `Hello, ${arg.foo}!`; } const myVar = { foo: "World", bar: 42 }; console.log( myFunction( myVar ) ); // "Hello, World!"
Cette conversion est généralement effectuée de l'une des manières suivantes.
- Pour supprimer les informations de type du flux, le plugin babel est utilisé: @ babel / plugin-transform-flow-strip-types
- Vous pouvez utiliser l'une des deux solutions pour travailler avec TypeScript. Tout d'abord, on peut utiliser babel et le plugin @ babel / plugin-transform-typescript
- Deuxièmement, au lieu de babel, vous pouvez utiliser le propre transpilateur de Microsoft appelé tsc . Cet utilitaire est intégré au processus de création de l'application au lieu de babel.
Exemples de paramètres de projet pour flow et pour TypeScript (en utilisant tsc).
| Couler | Manuscrit |
|---|---|
| webpack.config.js | |
|
|
| Paramètres du transpilateur | |
| babel.config.js | tsconfig.json |
|
|
| .flowconfig | |
|
|
La différence entre les approches babel + strip et tsc est faible en termes d'assemblage. Dans le premier cas, babel est utilisé, dans le second, ce sera tsc.
Mais il y a une différence si un utilitaire comme eslint est utilisé. TypeScript pour linting avec eslint a son propre ensemble de plugins qui vous permettent de trouver encore plus de bogues. Mais ils exigent qu'au moment de l'analyse par le linter, il dispose d'informations sur les types de variables. Pour ce faire, seul tsc doit être utilisé comme analyseur de code, pas babel. Mais si tsc est utilisé pour le linter, alors il sera erroné d'utiliser babel pour la construction (le zoo des utilitaires utilisé devrait être minime!).
| Couler | Manuscrit |
|---|---|
| .eslint.js | |
|
|
Types pour les bibliothèques
Lorsqu'une bibliothèque est publiée dans le référentiel npm, c'est la version JavaScript qui est publiée. On suppose que le code publié n'a pas besoin d'être modifié pour être utilisé dans un projet. Autrement dit, le code a déjà passé la traspilation nécessaire via babel ou tsc. Mais alors les informations sur les types dans le code sont déjà perdues. Que faire?
Dans le flux, on suppose qu'en plus de la version JavaScript "pure", la bibliothèque contiendra des fichiers avec l'extension
.js.flow
contenant le code de flux source avec toutes les définitions de type. Ensuite, lors de l'analyse du flux, il pourra connecter ces fichiers pour la vérification de type, et lors de la construction du projet et de son exécution, ils seront ignorés - des fichiers JS ordinaires seront utilisés. Vous pouvez ajouter des fichiers .flow à la bibliothèque par simple copie. Cependant, cela augmentera considérablement la taille de la bibliothèque en npm.
Dans TypeScript, il n'est pas suggéré de conserver les fichiers source côte à côte, mais uniquement une liste de définitions. S'il y a un fichier
myModule.js
, lors de l'analyse du projet, TypeScript recherchera un fichier à proximité
myModule.js.d.ts
, dans lequel il s'attend à voir des définitions (mais pas du code!) De tous les types, fonctions et autres éléments nécessaires pour analyser les types. Le transpilateur tsc est capable de créer lui-même de tels fichiers à partir du TypeScript source (voir l'option
declaration
dans la documentation).
Types pour les bibliothèques héritées
Pour les flux et TypeScript, il existe un moyen d'ajouter des déclarations de type pour les bibliothèques qui ne contiennent pas initialement ces descriptions. Mais cela se fait de différentes manières.
Pour le flux, il n'existe pas de méthode «native» prise en charge par Facebook lui-même. Mais il existe un projet de type flux qui collecte ces définitions dans son référentiel. En fait, un moyen parallèle pour npm de version de ces définitions, et aussi un moyen "centralisé" de mise à jour pas très pratique.
Dans TypeScript, la manière standard d'écrire de telles définitions est de les publier dans des packages npm spéciaux avec le préfixe "@types"... Pour ajouter une description de types pour une bibliothèque à votre projet, il suffit de connecter la @ types-library correspondante, par exemple,
@types/react
pour React ou
@types/chai
pour chai.
Comparaison du flux et de TypeScript
Une tentative de comparer le flux et TypeScript. Des faits sélectionnés sont rassemblés à partir de l'article de Nathan Sebhastian "TypeScript VS Flow", certains sont collectés indépendamment.
Prise en charge native sur différents frameworks. Natif - pas d'approche supplémentaire avec un fer à souder et des bibliothèques et plugins tiers.
Divers dirigeants
| Couler | Manuscrit | |
|---|---|---|
| Contributeur principal | Microsoft | |
| Site Internet | flow.org | www.typescriptlang.org |
| Github | github.com/facebook/flow | github.com/microsoft/TypeScript |
| GitHub démarre | 21,3 km | 70,1 km |
| Fourches GitHub | 1,8 km | 9,2 km |
| Problèmes GitHub: ouvert / fermé | 2,4k / 4,1k | 4,9k / 25,0k |
| StackOverflow actif | 2289 | 146 221 |
| StackOverflow fréquent | 123 | 11451 |
En regardant ces chiffres, je n'ai tout simplement pas le droit moral de recommander l'utilisation du flux. Mais pourquoi l'ai-je utilisé moi-même? Parce qu'il existait autrefois le flow-runtime.
flow-runtime
flow-runtime est un ensemble de plugins pour babel qui vous permet d'incorporer des types de flux dans le runtime, de les utiliser pour définir des types de variables au moment de l'exécution et, surtout pour moi, de vérifier les types de variables au moment de l'exécution. Cela permettait lors de l'exécution, par exemple, des autotests ou des tests manuels, de détecter des bogues supplémentaires dans l'application.
C'est-à-dire qu'au moment de l'exécution (dans l'assemblage de débogage, bien sûr), l'application a vérifié explicitement tous les types de variables, arguments, résultats d'appels à des fonctions tierces, et tout, tout, tout, pour la conformité avec ces types.
Malheureusement, pour la nouvelle année 2021, l'auteur du référentiel a ajouté des informationsqu'il n'est plus impliqué dans le développement de ce projet et, en général, passe à TypeScript. En fait, la dernière raison de rester sur le flux est devenue obsolète pour moi. Eh bien, bienvenue dans TypeScript.