Warum statische Typen in JavaScript verwenden? (Ein 4-teiliger Primer zur statischen Typisierung mit Flow)

Als JavaScript-Entwickler können Sie den ganzen Tag codieren, ohne auf statische Typen zu stoßen. Warum also etwas über sie lernen?

Nun, es stellt sich heraus, dass Lerntypen nicht nur eine Übung zur Bewusstseinserweiterung sind. Wenn Sie bereit sind, etwas Zeit in das Erlernen der Vor- und Nachteile und Anwendungsfälle statischer Typen zu investieren, kann dies Ihrer Programmierung immens helfen.

Interessiert? Nun, Sie haben Glück - darum geht es im Rest dieser vierteiligen Serie.

Zunächst eine Definition

Der schnellste Weg, statische Typen zu verstehen, besteht darin, sie dynamischen Typen gegenüberzustellen. Eine Sprache mit statischen Typen wird als statisch typisierte Sprache bezeichnet . Andererseits wird eine Sprache mit dynamischen Typen als dynamisch typisierte Sprache bezeichnet.

Der Hauptunterschied besteht darin, dass statisch typisierte Sprachen zur Kompilierungszeit eine Typprüfung durchführen , während dynamisch typisierte Sprachen zur Laufzeit eine Typprüfung durchführen .

Damit bleibt Ihnen noch ein Konzept: Was bedeutet „ Typprüfung“ ?

Schauen wir uns zur Erklärung die Typen in Java im Vergleich zu Javascript an.

"Typen" bezieht sich auf den Datentyp, der definiert wird.

Zum Beispiel in Java, wenn Sie Folgendes definieren booleanals:

boolean result = true;

Dies hat einen korrekten Typ, da die booleanAnmerkung mit dem angegebenen Wert übereinstimmt result, im Gegensatz zu einer Ganzzahl oder etwas anderem.

Auf der anderen Seite, wenn Sie versucht haben zu erklären:

boolean result = 123;

… Dies würde nicht kompiliert werden können, da es einen falschen Typ hat. Es markiert explizit resultals boolean, definiert es dann aber als Ganzzahl 123.

JavaScript und andere dynamisch typisierte Sprachen verfolgen einen anderen Ansatz, sodass der Kontext festlegen kann, welcher Datentyp definiert wird:

var result = true;

Lange Rede, kurzer Sinn: Bei statisch typisierten Sprachen müssen Sie die Datentypen Ihrer Konstrukte deklarieren, bevor Sie sie verwenden können. Dynamisch typisierte Sprachen nicht. JavaScript impliziert den Datentyp, während Java ihn direkt angibt.

So wie Sie sehen können, Typen können Sie Programm angeben Invarianten oder die logischen Aussagen und Bedingungen , unter denen das Programm auszuführen.

Die Typprüfung überprüft und erzwingt, dass der Typ eines Konstrukts (Konstante, Boolescher Wert, Zahl, Variable, Array, Objekt) mit einer von Ihnen angegebenen Invariante übereinstimmt. Sie können beispielsweise angeben, dass "diese Funktion immer eine Zeichenfolge zurückgibt". Wenn das Programm ausgeführt wird, können Sie davon ausgehen, dass es eine Zeichenfolge zurückgibt.

Die Unterschiede zwischen statischer und dynamischer Typprüfung sind am wichtigsten, wenn ein Typfehler auftritt. In einer statisch typisierten Sprache treten Typfehler während des Kompilierungsschritts, dh zur Kompilierungszeit, auf. In dynamisch typisierten Sprachen treten die Fehler erst auf, wenn das Programm ausgeführt wird. Das heißt, zur Laufzeit .

Dies bedeutet, dass ein Programm, das in einer dynamisch typisierten Sprache (wie JavaScript oder Python) geschrieben ist, auch dann kompiliert werden kann, wenn es Typfehler enthält, die andernfalls die ordnungsgemäße Ausführung des Skripts verhindern würden.

Wenn ein Programm, das in einer statisch typisierten Sprache (wie Scala oder C ++) geschrieben ist, Typfehler enthält, kann es erst kompiliert werden, wenn die Fehler behoben wurden.

Eine neue Ära von JavaScript

Da JavaScript eine dynamisch typisierte Sprache ist, können Sie Variablen, Funktionen, Objekte und alles andere deklarieren, ohne den Typ zu deklarieren.

Praktisch, aber nicht immer ideal. Aus diesem Grund haben Tools wie Flow und TypeScript kürzlich JavaScript-Entwicklern die * Option * gegeben, statische Typen zu verwenden.

Flow ist eine von Facebook entwickelte und veröffentlichte Open-Source-Bibliothek zur statischen Typprüfung, mit der Sie Ihrem JavaScript-Code schrittweise Typen hinzufügen können.

TypeScript hingegen ist eine Obermenge, die bis zu JavaScript kompiliert wird - obwohl es sich fast wie eine neue statisch typisierte Sprache anfühlt. Das heißt, es sieht und fühlt sich JavaScript sehr ähnlich und ist nicht schwer zu erlernen.

In beiden Fällen teilen Sie dem Tool explizit mit, welche Dateien überprüft werden sollen, wenn Sie Typen verwenden möchten. Für TypeScript tun Sie dies, indem Sie Dateien mit der .tsErweiterung anstelle von schreiben .js. Für Flow fügen Sie einen Kommentar oben in die Datei mit ein@flow

Sobald Sie erklärt haben, dass Sie eine Datei typüberprüfen möchten, können Sie die entsprechende Syntax zum Definieren von Typen verwenden. Ein Unterschied zwischen den beiden Tools besteht darin, dass Flow ein Typ "Checker" und kein Compiler ist. TypeScript hingegen ist ein Compiler.

Ich bin der festen Überzeugung, dass Tools wie Flow und TypeScript einen Generationswechsel und Fortschritt für JavaScript darstellen.

Persönlich habe ich so viel gelernt, indem ich in meinem Alltag Typen verwendet habe. Deshalb hoffe ich, dass Sie mich auf dieser kurzen und süßen Reise in statische Typen begleiten.

Der Rest dieses 4-teiligen Beitrags behandelt:

Teil I. Eine kurze Einführung in die Flow-Syntax und -Sprache

Teile II & III. Vor- und Nachteile statischer Typen (mit detaillierten Durchgangsbeispielen)

Teil IV. Sollten Sie statische Typen in JavaScript verwenden oder nicht?

Beachten Sie, dass ich in den Beispielen in diesem Beitrag Flow anstelle von TypeScript ausgewählt habe, da ich damit vertraut bin. Bitte recherchieren Sie für Ihre eigenen Zwecke und wählen Sie das richtige Werkzeug für Sie aus. TypeScript ist auch fantastisch.

Fangen wir ohne weiteres an!

Teil 1: Eine kurze Einführung in die Flow-Syntax und -Sprache

Um die Vor- und Nachteile statischer Typen zu verstehen, sollten Sie zunächst ein grundlegendes Verständnis der Syntax für statische Typen mithilfe von Flow erlangen. Wenn Sie noch nie in einer statisch typisierten Sprache gearbeitet haben, kann es einige Zeit dauern, bis sich die Syntax daran gewöhnt hat.

Lassen Sie uns zunächst untersuchen, wie Sie JavaScript-Grundelementen Typen sowie Konstrukte wie Arrays, Objekte, Funktionen usw. hinzufügen.

Boolescher Wert

Dies beschreibt einen boolean(wahren oder falschen) Wert in JavaScript.

Beachten Sie, dass Sie beim Angeben eines Typs folgende Syntax verwenden:

Nummer

Dies beschreibt eine IEEE 754-Gleitkommazahl. Im Gegensatz zu vielen anderen Programmiersprachen definiert JavaScript keine unterschiedlichen Arten von Zahlen (wie Ganzzahlen, Kurz-, Lang- und Gleitkommazahlen). Stattdessen werden Zahlen immer als Gleitkommazahlen mit doppelter Genauigkeit gespeichert. Daher benötigen Sie nur einen Nummerntyp, um eine Nummer zu definieren.

numberbeinhaltet Infinityund NaN.

Zeichenfolge

Dies beschreibt eine Zeichenfolge.

Null

Dies beschreibt den nullDatentyp in JavaScript.

Leere

Dies beschreibt den undefinedDatentyp in JavaScript.

Beachten Sie, dass nullund undefinedunterschiedlich behandelt werden. Wenn Sie versucht haben:

Flow würde einen Fehler auslösen, da der Typ vom Typ sein voidsoll, der undefinednicht mit dem Typ identisch ist null.

Array

Beschreibt ein JavaScript-Array. Sie verwenden die Syntax Array<; T>, um ein Array zu beschreiben, dessen Elemente vom Typ T sind.

Beachten Sie, wie ich ersetzt Tmit string, das heißt ich bin erklärt messagesals ein Array von Strings.

Objekt

Dies beschreibt ein JavaScript-Objekt. Es gibt verschiedene Möglichkeiten, Objekten Typen hinzuzufügen.

Sie können Typen hinzufügen, um die Form eines Objekts zu beschreiben:

Sie können Objekte als Karten definieren, in denen Sie eine Zeichenfolge einem bestimmten Wert zuordnen:

Sie können ein Objekt auch als ObjectTyp definieren:

Mit diesem letzten Ansatz können wir jeden Schlüssel und Wert für Ihr Objekt ohne Einschränkung festlegen, sodass bei der Typprüfung nicht wirklich viel Wert hinzugefügt wird.

irgendein

Dies kann buchstäblich jeden Typ darstellen. Der anyTyp ist effektiv deaktiviert, daher sollten Sie versuchen, die Verwendung zu vermeiden, es sei denn, dies ist unbedingt erforderlich (z. B. wenn Sie die Typprüfung deaktivieren müssen oder eine Notluke benötigen).

Eine Situation, für die Sie möglicherweise anynützlich sind, ist die Verwendung einer externen Bibliothek, die die Prototypen eines anderen Systems erweitert (z. B. Object.prototype).

Wenn Sie beispielsweise eine Bibliothek verwenden, die Object.prototype um eine doSomethingEigenschaft erweitert:

Möglicherweise wird eine Fehlermeldung angezeigt:

Um dies zu umgehen, können Sie Folgendes verwenden any:

Funktionen

Die häufigste Methode zum Hinzufügen von Typen zu Funktionen besteht darin, den Eingabeargumenten und (falls relevant) den Rückgabewert Typen hinzuzufügen:

Sie können asynchronen Funktionen (siehe unten) und Generatoren sogar Typen hinzufügen:

Beachten Sie, wie unser zweiter Parameter getPurchaseLimitals eine Funktion kommentiert wird, die a zurückgibt Promise. Und amountExceedsPurchaseLimitwird kommentiert als auch ein Promise.

Geben Sie einen Alias ​​ein

Typ-Aliasing ist eine meiner bevorzugten Möglichkeiten, statische Typen zu verwenden. Mit ihnen können Sie vorhandene Typen (Nummer, Zeichenfolge usw.) verwenden, um neue Typen zu erstellen:

Oben habe ich einen neuen Typ namens erstellt, PaymentMethodder Eigenschaften enthält, die aus numberund stringTypen bestehen.

Wenn Sie nun den PaymentMethodTyp verwenden möchten , können Sie Folgendes tun:

Sie können auch Typ-Aliase für jedes Grundelement erstellen, indem Sie den zugrunde liegenden Typ in einen anderen Typ einschließen. Wenn Sie beispielsweise den Alias ​​a Nameund eingeben möchten EmailAddress:

Auf diese Weise zeigen Sie dies an Nameund Emailsind unterschiedliche Dinge, nicht nur Zeichenfolgen. Da ein Name und eine E-Mail nicht wirklich austauschbar sind, können Sie sie dadurch nicht versehentlich verwechseln.

Generika

Generika sind eine Möglichkeit, über die Typen selbst zu abstrahieren. Was bedeutet das?

Lass uns einen Blick darauf werfen:

Ich habe eine Abstraktion für den Typ erstellt T. Jetzt können Sie jeden Typ verwenden, den Sie darstellen möchten T. Für numberT, Twar der Typ number. Inzwischen war arrayTT vom TypArrayer>.

Yes, I know. It’s dizzying stuff if this is the first time you’re looking at types. I promise the “gentle” intro is almost over!

Maybe

Maybe type allows us to type annotate a potentially null or undefined value. They have the type T|void|null for some type T, meaning it is either type T or it is undefined or null. To define a maybe type, you put a question mark in front of the type definition:

Here I’m saying that message is either a string, or it’s null or undefined.

You can also use maybe to indicate that an object property will be either of some type T or undefined:

By putting the ? next to the property name for middleInitial, you can indicate that this field is optional.

Disjoint unions

This is another powerful way to model data. Disjoint unions are useful when you have a program that needs to deal with different kinds of data all at once. In other words, the shape of the data can be different based on the situation.

Extending on the PaymentMethod type from our earlier generics example, let’s say that you have an app where users can have one of three types of payment methods. In this case, you can do something like:

Then you can define your PaymentMethod type as a disjoint union with three cases.

Payment method now can only ever be one of these three shapes. The property type is the property that makes the union type “disjoint”.

You’ll see more practical examples of disjoint union types later in part II.

All right, almost done. There are a couple other features of Flow worth mentioning before concluding this intro:

1) Type inference: Flow uses type inference where possible. Type inference kicks in when the type checker can automatically deduce the data type of an expression. This helps avoid excessive annotation.

For example, you can write:

Even though this Class doesn’t have types, Flow can adequately type check it:

Here I’ve tried to define area as a string, but in the Rectangle class definition we defined width and height as numbers. So based on the function definition for area, it must be return a number. Even though I didn’t explicitly define types for the area function, Flow caught the error.

One thing to note is that the Flow maintainers recommend that if you were exporting this class definition, you’d want to add explicit type definitions to make it easier to find the cause of errors when the class is not used in a local context.

2) Dynamic type tests: What this basically means is that Flow has logic to determine what the the type of a value will be at runtime and so is able to use that knowledge when performing static analysis. They become useful in situations like when Flow throws an error but you need to convince flow that what you’re doing is right.

I won’t go into too much detail because it’s more of an advanced feature that I hope to write about separately, but if you want to learn more, it’s worth reading through the docs.

We’re done with syntax

We covered a lot of ground in one section! I hope this high-level overview has been helpful and manageable. If you’re curious to go deeper, I encourage you to dive into the well-written docs and explore.

With syntax out of the way, let’s finally get to the fun part: exploring the advantages and disadvantages of using types!

Next up: Part 2 & 3.

Original text