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 booleansom:

boolean result = true;

Dette har en korrekt type, fordi booleankommentaren 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 resultsom 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 .tsudvidelsen 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.

numberinkluderer Infinityog NaN.

snor

Dette beskriver en streng.

nul

Dette beskriver nulldatatypen i JavaScript.

ugyldig

Dette beskriver undefineddatatypen i JavaScript.

Bemærk, at nullog undefinedbehandles forskelligt. Hvis du forsøgte at gøre:

Flow vil kaste en fejl, fordi typen voidformodes at være af typen, undefinedsom 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 Tmed string, hvilket betyder at jeg erklærer messagessom 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 Objecttype:

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 anytype 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 anynyttig 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 doSomethingegenskab:

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 getPurchaseLimiter kommenteret som en funktion, der returnerer en Promise. Og amountExceedsPurchaseLimiter 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 PaymentMethodsom har egenskaber der består af numberog stringtyper.

Hvis du nu vil bruge PaymentMethodtypen, 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 Nameog EmailAddress:

Ved at gøre dette angiver du det Nameog Emailer 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, Tvar af typen number. I mellemtiden var arrayTT af typenArray er>.

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.