Hvorfor bruge statiske typer i JavaScript? (En 4-delt primer til statisk typning med Flow)

Som JavaScript-udvikler kan du kode hele dagen uden at støde på statiske typer. Så hvorfor gider at lære om dem?
Nå viser det sig, at indlæringstyper ikke kun er en øvelse i sind-ekspansion. Hvis du er villig til at investere lidt tid i at lære om statiske typer fordele, ulemper og brugssager, kan det hjælpe din programmering enormt.
Interesseret? Nå har du held og lykke - det er det, resten af denne firedelte serie handler om.
For det første en definition
Den hurtigste måde at forstå statiske typer er at kontrastere dem med dynamiske typer. Et sprog med statiske typer omtales som et statisk-skrevet sprog . På den anden side kaldes et sprog med dynamiske typer et dynamisk typet sprog.
Kerneforskellen er, at statisk typede sprog udfører typekontrol ved kompileringstid , mens dynamisk typede sprog udfører typekontrol ved kørselstid .
Dette efterlader endnu et koncept, du kan tackle: hvad betyder " typekontrol" ?
For at forklare, lad os se på typer i Java versus Javascript.
"Typer" henviser til den type data, der defineres.
For eksempel i Java, hvis du definerer en boolean
som:
boolean result = true;
Dette har en korrekt type, fordi boolean
kommentaren matcher den værdi, der er givet til result
, i modsætning til et heltal eller noget andet.
På den anden side, hvis du forsøgte at erklære:
boolean result = 123;
... dette kunne ikke kompileres, fordi det har en forkert type. Det markerer eksplicit result
som a boolean
, men definerer det derefter som heltal 123
.
JavaScript og andre dynamisk typede sprog har en anden tilgang, der gør det muligt for konteksten at fastslå, hvilken type data der defineres:
var result = true;
Lang historie kort: statisk typede sprog kræver, at du erklærer datatyperne for dine konstruktioner, før du kan bruge dem. Dynamisk typede sprog gør det ikke. JavaScript indebærer datatypen, mens Java angiver det direkte.
Så som du kan se, typer giver dig mulighed for at angive programmet invarianter , eller de logiske påstande og betingelser, hvorunder programmet vil udføre.
Typekontrol verificerer og håndhæver, at typen af en konstruktion (konstant, boolsk, tal, variabel, matrix, objekt) matcher en invariant, som du har angivet. Du kan f.eks. Angive, at "denne funktion altid returnerer en streng." Når programmet kører, kan du med sikkerhed antage, at det returnerer en streng.
Forskellene mellem statisk typekontrol og dynamisk typekontrol betyder mest, når der opstår en typefejl. På et statisk-skrevet sprog opstår der typefejl under kompileringstrinnet, dvs. på kompileringstidspunktet. På dynamisk typede sprog opstår fejlene kun, når programmet er udført. Det vil sige ved runtime .
Dette betyder, at et program skrevet på et dynamisk skrevet sprog (som JavaScript eller Python) kan kompilere, selvom det indeholder typefejl, der ellers ville forhindre scriptet i at køre korrekt.
På den anden side, hvis et program skrevet på et statisk-skrevet sprog (som Scala eller C ++) indeholder typefejl, vil det ikke kompilere, før fejlene er rettet.
En ny æra af JavaScript
Fordi JavaScript er et dynamisk skrevet sprog, kan du gå omkring til at erklære variabler, funktioner, objekter og andet uden at erklære typen.
Praktisk, men ikke altid ideel. Derfor er værktøjer som Flow og TypeScript for nylig trådt ind for at give JavaScript-udviklere * mulighed * for at bruge statiske typer.
Flow er et open source-system til kontrol af statisk type, der er udviklet og udgivet af Facebook, der giver dig mulighed for trinvist at tilføje typer til din JavaScript-kode.
TypeScript er derimod et supersæt, der kompileres ned til JavaScript - selvom det føles næsten som et nyt statisk-skrevet sprog i sig selv. Når det er sagt, ser det ud og føles meget lig JavaScript og er ikke svært at hente.
I begge tilfælde, når du vil bruge typer, fortæller du eksplicit værktøjet om, hvilke filer der skal typecheckes. For TypeScript gør du dette ved at skrive filer med .ts
udvidelsen i stedet for .js
. For Flow inkluderer du en kommentar oven på filen med@flow
Når du har erklæret, at du vil skrive en fil, skal du bruge deres respektive syntaks til at definere typer. En skelnen mellem de to værktøjer er, at Flow er en type "checker" og ikke en compiler. TypeScript er derimod en kompilator.
Jeg tror virkelig, at værktøjer som Flow og TypeScript præsenterer et generationsskift og fremskridt for JavaScript.
Personligt har jeg lært så meget ved at bruge typer i min dag-til-dag. Derfor håber jeg, at du vil slutte mig til denne korte og søde rejse til statiske typer.
Resten af dette 4-delte indlæg vil dække:
Del I. En hurtig introduktion til Flow-syntaks og sprog
Del II og III. Fordele og ulemper ved statiske typer (med detaljerede gennemgangseksempler)
Del IV. Skal du bruge statiske typer i JavaScript eller ej?
Bemærk, at jeg valgte Flow over TypeScript i eksemplerne i dette indlæg på grund af min fortrolighed med det. Til dine egne formål bedes du undersøge og vælge det rigtige værktøj til dig. TypeScript er også fantastisk.
Uden yderligere ado, lad os begynde!
Del 1: En hurtig introduktion til Flow-syntaks og sprog
For at forstå fordele og ulemper ved statiske typer, skal du først få en grundlæggende forståelse af syntaksen for statiske typer ved hjælp af Flow. Hvis du aldrig har arbejdet på et statisk-skrevet sprog før, kan det tage lidt tid at vænne sig til syntaksen.
Lad os begynde med at undersøge, hvordan man tilføjer typer til JavaScript-primitiver såvel som konstruktioner som Arrays, Object, Funktioner osv.
boolsk
Dette beskriver en boolean
(sand eller falsk) værdi i JavaScript.
Bemærk, at når du vil specificere en type, er den syntaks, du bruger:

nummer
Dette beskriver et IEEE 754 flydende nummer. I modsætning til mange andre programmeringssprog definerer JavaScript ikke forskellige typer numre (som heltal, korte, lange og flydende punkter). I stedet gemmes tal altid som flydende punktnumre med dobbelt præcision. Derfor har du kun brug for en nummertype for at definere et hvilket som helst nummer.
number
inkluderer Infinity
og NaN
.
snor
Dette beskriver en streng.
nul
Dette beskriver null
datatypen i JavaScript.
ugyldig
Dette beskriver undefined
datatypen i JavaScript.
Bemærk, at null
og undefined
behandles forskelligt. Hvis du forsøgte at gøre:
Flow vil kaste en fejl, fordi typen void
formodes at være af typen, undefined
som ikke er den samme som typen null
.
Array
Beskriver et JavaScript-array. Du bruger syntaksen Array<
; T> til at beskrive et array, hvis elementer er af en eller anden type T.
Bemærk hvordan jeg erstattede T
med string
, hvilket betyder at jeg erklærer messages
som en række strenge.
Objekt
Dette beskriver et JavaScript-objekt. Der er et par forskellige måder at tilføje typer til objekter på.
Du kan tilføje typer til at beskrive formen på et objekt:
Du kan definere objekter som kort, hvor du kortlægger en streng til en værdi:
Du kan også definere et objekt som en Object
type:
Denne sidste tilgang lader os indstille enhver nøgle og værdi på dit objekt uden begrænsning, så det tilføjer ikke rigtig meget værdi for så vidt angår typekontrol.
nogen
Dette kan bogstaveligt talt repræsentere enhver type. Den any
type er effektivt markeret, så du bør forsøge at undgå at bruge det, medmindre det er absolut nødvendigt (ligesom når du har brug for at fravælge typen kontrol eller har brug for en flugt luge).
En situation, som du måske finder any
nyttig til, er når du bruger et eksternt bibliotek, der udvider et andet systems prototyper (som Object.prototype).
For eksempel, hvis du bruger et bibliotek, der udvider Object.prototype med en doSomething
egenskab:
Du får muligvis en fejl:
For at omgå dette kan du bruge any
:
Funktioner
Den mest almindelige måde at tilføje typer til funktioner på er at føje typer til dets inputargumenter og (når det er relevant) returværdien:
Du kan endda tilføje typer til asynkroniseringsfunktioner (se nedenfor) og generatorer:
Læg mærke til, hvordan vores anden parameter getPurchaseLimit
er kommenteret som en funktion, der returnerer en Promise
. Og amountExceedsPurchaseLimit
er kommenteret som også at returnere a Promise
.
Skriv alias
Type aliasing er en af mine foretrukne måder at bruge statiske typer på. De giver dig mulighed for at bruge eksisterende typer (nummer, streng osv.) Til at komponere nye typer:
Ovenfor oprettede jeg en ny type kaldet PaymentMethod
som har egenskaber der består af number
og string
typer.
Hvis du nu vil bruge PaymentMethod
typen, kan du gøre:
Du kan også oprette typealiaser til enhver primitiv ved at indpakke den underliggende type i en anden type. Hvis du f.eks. Vil skrive alias a Name
og EmailAddress
:
Ved at gøre dette angiver du det Name
og Email
er forskellige ting, ikke kun strenge. Da et navn og en e-mail ikke rigtig kan udskiftes, forhindrer du det ved ikke at blande dem ved et uheld.
Generiske stoffer
Generiske er en måde at abstrakte over selve typerne på. Hvad betyder det?
Lad os se:
Jeg oprettede en abstraktion for typen T
. Nu kan du bruge den type, du vil repræsentere T
. For numberT
, T
var af typen number
. I mellemtiden var arrayT
T af typenArray
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.