Leider müssen wir zugeben, dass Flow im Jahr 2021 TypeScript sowohl in Bezug auf die Popularität als auch in Bezug auf die Unterstützung durch eine Vielzahl von Dienstprogrammen (und Bibliotheken) bereits erheblich unterlegen ist, und es ist an der Zeit
Warum benötigen Sie Typensicherheit in JavaScript?
JavaScript ist eine wunderbare Sprache. Nein, nicht so. Das Ökosystem rund um JavaScript ist großartig. Für 2021 bewundert sie wirklich die Tatsache, dass Sie die modernsten Funktionen der Sprache verwenden und dann durch Ändern einer Einstellung des Build-Systems die ausführbare Datei transpilieren können, um ihre Ausführung in älteren Versionen von Browsern, einschließlich IE8, zu unterstützen , es wird nicht bei Nacht sein, erinnere dich. Sie können "in HTML schreiben" (dh JSX) und dann mit dem Dienstprogramm
babel
(oder
tsc
) alle Tags durch korrekte JavaScript-Konstrukte ersetzen, z. B. das Aufrufen der React-Bibliothek (oder eines anderen, aber mehr dazu in einem anderen Beitrag).
Warum eignet sich JavaScript als Skriptsprache, die in Ihrem Browser ausgeführt wird?
- JavaScript muss nicht "kompiliert" werden. Sie fügen einfach JavaScript-Konstrukte hinzu und der Browser muss sie verstehen. Dies gibt sofort eine Reihe von praktischen und fast kostenlosen Dingen. Zum Beispiel das Debuggen direkt im Browser, was nicht in der Verantwortung des Programmierers liegt (der beispielsweise nicht vergessen darf, eine Reihe von Compiler-Debugging-Optionen und entsprechenden Bibliotheken einzuschließen), sondern des Browser-Entwicklers. Sie müssen nicht 10 bis 30 Minuten warten (Echtzeit für C / C ++), während Ihr 10k-Zeilenprojekt kompiliert wird, um zu versuchen, etwas anderes zu schreiben. Sie ändern einfach die Zeile, laden die Browserseite neu und beobachten das neue Verhalten des Codes. Und wenn Sie beispielsweise ein Webpack verwenden, wird die Seite auch für Sie neu geladen. In vielen Browsern können Sie den Code direkt auf der Seite mit ihren devtools ändern.
- - . 2021 . Chrome/Firefox, , , 5% (enterprise-) 30% (UI/) , .
- JavaScript , . — ( worker'). , 100% CPU ( UI ), , , Promise/async/await/etc.
- Gleichzeitig denke ich nicht einmal an die Frage, warum JavaScript wichtig ist. Schließlich können Sie mit Hilfe von JS: Formulare validieren, den Seiteninhalt aktualisieren, ohne ihn vollständig neu zu laden, nicht standardmäßige Verhaltenseffekte hinzufügen, mit Audio und Video arbeiten und sogar den gesamten Client Ihrer Unternehmensanwendung in schreiben JavaScript.
Wie bei fast jeder Skriptsprache (interpretiert) können Sie in JavaScript ... fehlerhaften Code schreiben. Wenn der Browser diesen Code nicht erreicht, wird keine Fehlermeldung, keine Warnung und überhaupt nichts angezeigt. Einerseits ist das gut. Wenn Sie eine große, große Website haben, sollte selbst ein Syntaxfehler im Code des Button-Click-Handlers nicht dazu führen, dass die Site nicht vollständig vom Benutzer geladen wird.
Aber das ist natürlich schlecht. Denn die Tatsache, dass irgendwo auf der Website etwas nicht funktioniert, ist schlecht. Und es wäre großartig, bevor der Code auf eine funktionierende Site gelangt, alle Skripte auf der Site zu überprüfen und sicherzustellen, dass sie zumindest kompiliert werden. Und im Idealfall - und arbeiten. Hierfür werden verschiedene Dienstprogramme verwendet (mein Lieblingssatz ist npm + webpack + babel / tsc + karma + jsdom + mocha + chai).
Wenn wir in einer idealen Welt leben, werden alle Skripte auf Ihrer Website, auch einzeilige, mit Tests abgedeckt. Leider ist die Welt nicht ideal, und für all den Teil des Codes, der nicht durch Tests abgedeckt wird, können wir uns nur auf eine Art automatisierter Verifizierungswerkzeuge verlassen. Welches kann überprüfen:
- JavaScript. , JavaScript, , , . /// .
- . , , . , :
var x = null; x.foo();
. — null .
Zusätzlich zu Semantikfehlern kann es noch schrecklichere Fehler geben: logische Fehler. Wenn das Programm fehlerfrei läuft, aber das Ergebnis überhaupt nicht das ist, was erwartet wurde. Klassiker mit zusätzlichen Zeichenfolgen und Zahlen:
console.log( input.value ) // 1
console.log( input.value + 1 ) // 11
Bestehende statische Code-Analyse-Tools (z. B. eslint) können versuchen, eine erhebliche Anzahl potenzieller Fehler aufzuspüren, die ein Programmierer in seinem Code macht. Beispielsweise:
- Verbot einer Endlosschleife mit einer falschen Schleifenbeendigungsbedingung
- Async-Funktionen als Argumente für einen Promise-Konstruktor nicht zulassen
- Zuweisungen unter Bedingungen nicht zulassen
- und andere
Beachten Sie, dass alle diese Regeln im Wesentlichen Einschränkungen sind, die der Linter dem Programmierer auferlegt. Das heißt, der Linter reduziert tatsächlich die Fähigkeiten der JavaScript-Sprache, so dass der Programmierer weniger potenzielle Fehler macht. Wenn Sie All-All-Regeln aktivieren, ist es unmöglich, Zuweisungen unter Bedingungen vorzunehmen (obwohl JavaScript dies zunächst zulässt), doppelte Schlüssel in Objektliteralen zu verwenden und kann sogar nicht aufgerufen werden
console.log()
.
Das Hinzufügen von Variablentypen und die Typprüfung von Aufrufen sind zusätzliche Einschränkungen der JavaScript-Sprache, um potenzielle Fehler zu reduzieren.
Es wird versucht, eine Zahl mit einer Zeichenfolge zu multiplizieren
Ein Versuch, auf eine nicht vorhandene (im Typ nicht beschriebene) Eigenschaft eines Objekts zuzugreifen.
Ein Versuch, eine Funktion mit einem nicht übereinstimmenden Argumenttyp aufzurufen.
Wenn wir diesen Code ohne Typprüfung schreiben, wird der Code erfolgreich transpiliert. Kein Mittel zur statischen Code-Analyse kann diese Fehler nicht finden, wenn sie keine (expliziten oder impliziten) Informationen über die Objekttypen verwenden.
Das Hinzufügen von Eingabe zu JavaScript fügt dem vom Programmierer geschriebenen Code zusätzliche Einschränkungen hinzu, ermöglicht es Ihnen jedoch, Fehler zu finden, die sonst während der Skriptausführung auftreten würden (dh höchstwahrscheinlich im Browser des Benutzers).
JavaScript-Eingabefunktionen
| Fließen | Typoskript | |
|---|---|---|
| Möglichkeit, den Typ einer Variablen, ein Argument oder einen Rückgabetyp einer Funktion festzulegen | |
|
| Fähigkeit, Ihren Objekttyp (Schnittstelle) zu beschreiben | |
|
| Einschränken von Werten für einen Typ | |
|
| Separate Erweiterung auf Typebene für Aufzählungen |
|
|
| Typen "hinzufügen" |
|
|
| Zusätzliche "Typen" für komplexe Fälle |
|
|
Beide Engines für die Unterstützung von JavaScript-Typen verfügen ungefähr über die gleichen Funktionen. Wenn Sie jedoch aus stark typisierten Sprachen stammen, unterscheidet sich selbst typisiertes JavaScript erheblich von Java: Alle Typen beschreiben im Wesentlichen Schnittstellen, dh eine Liste von Eigenschaften (und deren Typen und / oder Argumente). Und wenn zwei Schnittstellen dieselben (oder kompatiblen) Eigenschaften beschreiben, können sie anstelle voneinander verwendet werden. Das heißt, der folgende Code ist in typisiertem JavaScript korrekt, in Java oder beispielsweise C ++ jedoch eindeutig falsch:
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!"
Dieser Code ist aus Sicht von typisiertem JavaScript korrekt, da für die MyTypeB-Schnittstelle eine Eigenschaft
foo
mit einem Typ erforderlich ist
string
, für eine Variable mit der MyTypeA-Schnittstelle.
Dieser Code kann mithilfe einer Literalschnittstelle für eine Variable etwas kürzer umgeschrieben werden
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!"
Der Variablentyp
myVar
in diesem Beispiel ist eine Literalschnittstelle
{ foo: string, bar: number }
. Es ist weiterhin mit der erwarteten Schnittstelle eines
arg
Funktionsarguments kompatibel
myFunction
, sodass dieser Code beispielsweise aus Sicht von TypeScript fehlerfrei ist.
Dieses Verhalten reduziert die Anzahl der Probleme beim Arbeiten mit verschiedenen Bibliotheken, benutzerdefiniertem Code und sogar beim Aufrufen von Funktionen erheblich. Ein typisches Beispiel ist, wenn eine Bibliothek gültige Optionen definiert und wir sie als Optionsobjekt übergeben:
// - interface OptionsType { optionA?: string; optionB?: number; } export function libFunction( arg: number, options = {} as OptionsType) { /*...*/ }
// import {libFunction} from "lib"; libFunction( 42, { optionA: "someValue" } );
Beachten Sie, dass der Typ
OptionsType
weder aus der Bibliothek exportiert noch in benutzerdefinierten Code importiert wird. Dies hindert Sie jedoch nicht daran, die Funktion über die Literalschnittstelle für das zweite Argument der
options
Funktion und für das Typisierungssystem aufzurufen , um dieses Argument auf Typkompatibilität zu überprüfen. Der Versuch, so etwas in Java zu tun, führt zu einer deutlichen Verwirrung unter den Compilern.
Wie funktioniert es aus Browsersicht?
Weder das TypeScript von Microsoft noch der Flow von Facebook werden von Browsern unterstützt. Ebenso haben die neuesten JavaScript-Spracherweiterungen in einigen Browsern noch keine Unterstützung gefunden. Wie wird dieser Code zum einen auf Richtigkeit überprüft und zum anderen vom Browser ausgeführt?
Die Antwort ist traspiling. Der gesamte "nicht standardmäßige" JavaScript-Code durchläuft eine Reihe von Dienstprogrammen, die den "nicht standardmäßigen" (Browsern unbekannten) Code in eine Reihe von Anweisungen verwandeln, die die Browser verstehen. Und für die Eingabe besteht die gesamte "Transformation" darin, dass alle Typverfeinerungen, alle Schnittstellenbeschreibungen, alle Einschränkungen aus dem Code einfach entfernt werden. Aus dem Code aus dem obigen Beispiel wird beispielsweise ...
/* : 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!"
jene.
function myFunction( arg ) { return `Hello, ${arg.foo}!`; } const myVar = { foo: "World", bar: 42 }; console.log( myFunction( myVar ) ); // "Hello, World!"
Diese Konvertierung erfolgt normalerweise auf eine der folgenden Arten.
- Um Typinformationen aus dem Flow zu entfernen, wird das babel-Plugin verwendet: @ babel / plugin-transform-flow-strip-types
- Sie können eine von zwei Lösungen verwenden, um mit TypeScript zu arbeiten. Erstens kann man babel und das @ babel / plugin-transform-typescript Plugin verwenden
- Zweitens können Sie anstelle von babel den Microsoft-eigenen Transpiler tsc verwenden . Dieses Dienstprogramm ist anstelle von babel in den Anwendungserstellungsprozess integriert .
Beispiele für Projekteinstellungen für Flow und TypeScript (mit tsc).
| Fließen | Typoskript |
|---|---|
| webpack.config.js | |
|
|
| Transpilereinstellungen | |
| babel.config.js | tsconfig.json |
|
|
| .flowconfig | |
|
|
Der Unterschied zwischen babel + strip- und tsc-Ansätzen ist in Bezug auf die Montage gering. Im ersten Fall wird babel verwendet, im zweiten Fall ist es tsc.
Es gibt jedoch einen Unterschied, wenn ein Dienstprogramm wie eslint verwendet wird. TypeScript zum Flusen mit eslint verfügt über eigene Plugins, mit denen Sie noch mehr Fehler finden können. Sie erfordern jedoch, dass der Linter zum Zeitpunkt der Analyse Informationen über die Variablentypen enthält. Zu diesem Zweck sollte nur tsc als Code-Parser verwendet werden, nicht babel. Wenn jedoch tsc für den Linter verwendet wird, ist es falsch, babel zum Bauen zu verwenden (der Zoo der verwendeten Dienstprogramme sollte minimal sein!).
| Fließen | Typoskript |
|---|---|
| .eslint.js | |
|
|
Typen für Bibliotheken
Wenn eine Bibliothek im npm-Repository veröffentlicht wird, wird die JavaScript-Version veröffentlicht. Es wird davon ausgegangen, dass der veröffentlichte Code nicht geändert werden muss, um in einem Projekt verwendet zu werden. Das heißt, der Code hat bereits die erforderliche Traspilation über babel oder tsc übergeben. Aber dann gehen die Informationen über die Typen im Code bereits verloren. Was zu tun ist?
Im Flow wird davon ausgegangen, dass die Bibliothek zusätzlich zur "reinen" JavaScript-Version Dateien mit der Erweiterung enthält
.js.flow
Enthält den Quellflusscode mit allen Typdefinitionen. Bei der Analyse des Datenflusses können diese Dateien dann zur Typprüfung verbunden werden. Beim Erstellen des Projekts und seiner Ausführung werden sie ignoriert. Es werden normale JS-Dateien verwendet. Sie können der Bibliothek Flow-Dateien hinzufügen, indem Sie sie einfach kopieren. Dies erhöht jedoch die Größe der Bibliothek in npm erheblich.
In TypeScript wird nicht empfohlen, die Quelldateien nebeneinander zu halten, sondern nur eine Liste von Definitionen. Wenn eine Datei vorhanden ist
myModule.js
, sucht TypeScript bei der Analyse des Projekts nach einer Datei in der Nähe
myModule.js.d.ts
, in der Definitionen (aber kein Code!) Von allen Typen, Funktionen und anderen Dingen angezeigt werden, die zum Analysieren von Typen erforderlich sind. Der tsc-Transpiler kann solche Dateien selbst aus dem Quell-TypeScript erstellen (siehe Option)
declaration
in der Dokumentation).
Typen für Legacy-Bibliotheken
Sowohl für Flow als auch für TypeScript gibt es eine Möglichkeit, Typdeklarationen für Bibliotheken hinzuzufügen, die diese Beschreibungen anfangs nicht enthalten. Aber es wird auf verschiedene Arten gemacht.
Für den Flow gibt es keine "native" Methode, die von Facebook selbst unterstützt wird. Es gibt jedoch ein Flow-typisiertes Projekt , das solche Definitionen in seinem Repository sammelt. In der Tat eine parallele Möglichkeit für npm, solche Definitionen zu versionieren, und auch keine sehr bequeme "zentralisierte" Art der Aktualisierung.
In TypeScript besteht die Standardmethode zum Schreiben solcher Definitionen darin, sie in speziellen npm-Paketen mit dem Präfix "@types" zu veröffentlichen... Um Ihrem Projekt eine Beschreibung der Typen für eine Bibliothek hinzuzufügen, reicht es aus, die entsprechende @ types-Bibliothek zu verbinden, z. B.
@types/react
für React oder
@types/chai
für Chai.
Vergleich von Flow und TypeScript
Ein Versuch, Flow und TypeScript zu vergleichen. Ausgewählte Fakten stammen aus Nathan Sebhastians Artikel "TypeScript VS Flow", einige werden unabhängig voneinander gesammelt.
Native Unterstützung über verschiedene Frameworks hinweg. Native - kein zusätzlicher Ansatz mit einem Lötkolben und Bibliotheken und Plugins von Drittanbietern.
Verschiedene Herrscher
| Fließen | Typoskript | |
|---|---|---|
| Hauptverantwortlicher | Microsoft | |
| Webseite | flow.org | www.typescriptlang.org |
| Github | github.com/facebook/flow | github.com/microsoft/TypeScript |
| GitHub startet | 21,3k | 70,1k |
| GitHub Gabeln | 1,8k | 9,2k |
| GitHub-Probleme: offen / geschlossen | 2,4 k / 4,1 k | 4,9 k / 25,0 k |
| StackOverflow Aktiv | 2289 | 146,221 |
| StackOverflow Häufig | 123 | 11451 |
Wenn ich mir diese Zahlen anschaue, habe ich einfach nicht das moralische Recht, Flow zur Verwendung zu empfehlen. Aber warum habe ich es selbst benutzt? Weil es früher so etwas wie Flow-Runtime gab.
Flow-Laufzeit
flow-runtime ist eine Reihe von Plugins für babel, mit denen Sie Flow-Typen in die Laufzeit einbetten, zur Laufzeit Variablentypen definieren und vor allem zur Laufzeit die Variablentypen überprüfen können. Dies ermöglichte es zur Laufzeit, beispielsweise während Autotests oder manuellen Tests, zusätzliche Fehler in der Anwendung zu erkennen.
Das heißt, direkt zur Laufzeit (natürlich in der Debug-Assembly) überprüfte die Anwendung explizit alle Arten von Variablen, Argumenten, Ergebnissen von Aufrufen von Funktionen von Drittanbietern und alles, alles, alles auf Übereinstimmung mit diesen Typen.
Leider hat der Autor des Repository für das neue Jahr 2021 Informationen hinzugefügtdass er nicht mehr an der Entwicklung dieses Projekts beteiligt ist und generell zu TypeScript wechselt. Tatsächlich wurde der letzte Grund, im Fluss zu bleiben, für mich veraltet. Willkommen bei TypeScript.