Po co używać typów statycznych w JavaScript? (4-składnikowy podkład do pisania statycznego za pomocą Flow)

Jako programista JavaScript możesz kodować przez cały dzień bez napotykania żadnych typów statycznych. Po co więc zawracać sobie głowę poznaniem ich?

Cóż, okazuje się, że typy uczenia się to nie tylko ćwiczenie rozwijające umysł. Jeśli chcesz poświęcić trochę czasu na poznanie zalet, wad i przypadków użycia typów statycznych, może to bardzo pomóc w programowaniu.

Zainteresowany? Cóż, masz szczęście - o tym jest reszta tej czteroczęściowej serii.

Najpierw definicja

Najszybszym sposobem zrozumienia typów statycznych jest zestawienie ich z typami dynamicznymi. Język z typami statycznymi jest nazywany językiem z typami statycznymi . Z drugiej strony język z typami dynamicznymi jest nazywany językiem z typami dynamicznymi .

Podstawowa różnica polega na tym, że języki z typowaniem statycznym sprawdzają typ w czasie kompilacji , podczas gdy języki z typowaniem dynamicznym sprawdzają typ w czasie wykonywania .

To pozostawia jeszcze jedną koncepcję do rozwiązania: co oznacza „ sprawdzanie typu” ?

Aby wyjaśnić, spójrzmy na typy w Javie i Javascript.

„Typy” odnoszą się do typu definiowanych danych.

Na przykład w Javie, jeśli zdefiniujesz booleanas:

boolean result = true;

Ma to poprawny typ, ponieważ booleanadnotacja pasuje do podanej wartości result, a nie do liczby całkowitej lub czegokolwiek innego.

Z drugiej strony, jeśli próbowałeś zadeklarować:

boolean result = 123;

… To się nie skompiluje, ponieważ ma nieprawidłowy typ. Wyraźnie oznacza to resultjako a boolean, ale następnie definiuje go jako liczbę całkowitą 123.

JavaScript i inne języki dynamicznie typowane mają inne podejście, pozwalając kontekstowi na ustalenie, jaki typ danych jest definiowany:

var result = true;

Krótko mówiąc: języki z typami statycznymi wymagają zadeklarowania typów danych konstrukcji, zanim będzie można ich użyć. Języki dynamicznie wpisywane nie. JavaScript implikuje typ danych, podczas gdy Java określa go wprost.

Jak widać, typy pozwalają na określenie niezmienników programu lub logicznych twierdzeń i warunków, w których program będzie wykonywany.

Sprawdzanie typów weryfikuje i wymusza, że ​​typ konstrukcji (stała, logiczna, liczba, zmienna, tablica, obiekt) jest zgodny z niezmiennikiem określonym przez użytkownika. Możesz na przykład określić, że „ta funkcja zawsze zwraca ciąg”. Po uruchomieniu programu można spokojnie założyć, że zwróci ciąg.

Różnice między statycznym sprawdzaniem typu a dynamicznym sprawdzaniem typu mają największe znaczenie w przypadku wystąpienia błędu typu. W języku z typowaniem statycznym błędy typu występują na etapie kompilacji, czyli w czasie kompilacji. W językach dynamicznie wpisywanych błędy pojawiają się dopiero po wykonaniu programu. Oznacza to, że w czasie wykonywania .

Oznacza to, że program napisany w języku dynamicznie wpisywanym (takim jak JavaScript lub Python) może się kompilować, nawet jeśli zawiera błędy typu, które w przeciwnym razie uniemożliwiłyby poprawne działanie skryptu.

Z drugiej strony, jeśli program napisany w języku z typami statycznymi (takim jak Scala lub C ++) zawiera błędy typu, kompilacja nie powiedzie się, dopóki błędy nie zostaną naprawione.

Nowa era JavaScript

Ponieważ JavaScript jest językiem dynamicznie typowanym, możesz zadeklarować zmienne, funkcje, obiekty i cokolwiek bez deklarowania typu.

Wygodne, ale nie zawsze idealne. Dlatego ostatnio pojawiły się narzędzia takie jak Flow i TypeScript, aby dać programistom JavaScript * opcję * używania typów statycznych.

Flow to biblioteka typu open source do sprawdzania typów statycznych opracowana i wydana przez Facebooka, która umożliwia stopniowe dodawanie typów do kodu JavaScript.

Z drugiej strony, TypeScript jest nadzbiorem, który kompiluje się do JavaScript - chociaż sam w sobie wydaje się być nowym językiem z typowaniem statycznym. To powiedziawszy, wygląda i działa bardzo podobnie do JavaScript i nie jest trudne do odebrania.

W obu przypadkach, gdy chcesz użyć typów, wyraźnie poinformujesz narzędzie o tym, które pliki mają zostać sprawdzone. W przypadku języka TypeScript można to zrobić, pisząc pliki z .tsrozszerzeniem zamiast .js. W przypadku Flow dodajesz komentarz na górze pliku z@flow

Po zadeklarowaniu, że chcesz sprawdzić typ pliku, możesz użyć ich odpowiedniej składni do zdefiniowania typów. Jedną z różnic, jakie należy wprowadzić między tymi dwoma narzędziami, jest to, że Flow jest „kontrolerem” typów, a nie kompilatorem. Z drugiej strony, TypeScript jest kompilatorem.

Naprawdę wierzę, że narzędzia takie jak Flow i TypeScript zapewniają zmianę pokoleniową i postęp w JavaScript.

Osobiście wiele się nauczyłem, używając typów na co dzień. Dlatego mam nadzieję, że dołączysz do mnie w tej krótkiej i słodkiej podróży w statyczne typy.

Reszta tego 4-częściowego postu będzie dotyczyła:

Część I. Szybkie wprowadzenie do składni i języka Flow

Część II i III. Zalety i wady typów statycznych (ze szczegółowymi przykładami przechodzenia)

Część IV. Należy używać typów statycznych w JavaScript, czy nie?

Zwróć uwagę, że wybrałem Flow zamiast TypeScript w przykładach w tym poście ze względu na moją znajomość z nim. Przeprowadź badania i wybierz odpowiednie dla siebie narzędzie do własnych celów. TypeScript jest również fantastyczny.

Bez zbędnych ceregieli zaczynamy!

Część 1: Szybkie wprowadzenie do składni i języka Flow

Aby zrozumieć zalety i wady typów statycznych, należy najpierw uzyskać podstawową wiedzę na temat składni typów statycznych przy użyciu Flow. Jeśli nigdy wcześniej nie pracowałeś w języku typowanym statycznie, przyzwyczajenie się do składni może zająć trochę czasu.

Zacznijmy od zbadania, jak dodawać typy do prymitywów JavaScript, a także konstrukcje, takie jak tablice, obiekt, funkcje itp.

boolean

Opisuje wartość boolean(prawda lub fałsz) w JavaScript.

Zauważ, że kiedy chcesz określić typ, używasz składni:

numer

Opisuje liczbę zmiennoprzecinkową IEEE 754. W przeciwieństwie do wielu innych języków programowania, JavaScript nie definiuje różnych typów liczb (takich jak liczby całkowite, krótkie, długie i zmiennoprzecinkowe). Zamiast tego liczby są zawsze przechowywane jako liczby zmiennoprzecinkowe o podwójnej precyzji. Dlatego do zdefiniowania dowolnej liczby potrzebny jest tylko jeden typ liczby.

numberzawiera Infinityi NaN.

strunowy

Opisuje ciąg.

zero

Opisuje nulltyp danych w JavaScript.

unieważnić

Opisuje undefinedtyp danych w JavaScript.

Zauważ, że nulli undefinedsą traktowane inaczej. Jeśli próbowałeś:

Przepływ zgłosiłby błąd, ponieważ typ voidpowinien być innego undefinedtypu niż typ null.

Szyk

Opisuje tablicę JavaScript. Używasz składni Array<; T>, aby opisać tablicę, której elementy są pewnego typu T.

Wskazówki jak wymieniłem Tz string, co oznacza, że jestem deklarując messagesjako tablica ciągów.

Obiekt

Opisuje obiekt JavaScript. Istnieje kilka różnych sposobów dodawania typów do obiektów.

Możesz dodać typy opisujące kształt obiektu:

Możesz zdefiniować obiekty jako mapy, na których mapujesz ciąg do jakiejś wartości:

Możesz również zdefiniować obiekt jako Objecttyp:

To ostatnie podejście pozwala nam ustawić dowolny klucz i wartość dla twojego obiektu bez ograniczeń, więc tak naprawdę nie dodaje dużej wartości, jeśli chodzi o sprawdzanie typu.

każdy

Może to reprezentować dosłownie dowolny typ. anyTyp jest skutecznie niekontrolowany, więc należy starać się unikać, chyba że jest to absolutnie konieczne (jak wtedy, gdy trzeba zrezygnować z kontroli typu lub potrzebują luku ratunkowego).

Jedna sytuacja, w której może się okazać anyprzydatna, to użycie zewnętrznej biblioteki, która rozszerza prototypy innego systemu (np. Object.prototype).

Na przykład, jeśli używasz biblioteki, która rozszerza Object.prototype o doSomethingwłaściwość:

Możesz otrzymać błąd:

Aby to obejść, możesz użyć any:

Funkcje

Najczęstszym sposobem dodawania typów do funkcji jest dodawanie typów do argumentów wejściowych i (w stosownych przypadkach) wartości zwracanej:

Możesz nawet dodawać typy do funkcji asynchronicznych (patrz poniżej) i generatorów:

Zwróć uwagę, jak nasz drugi parametr getPurchaseLimitjest oznaczony jako funkcja, która zwraca a Promise. I amountExceedsPurchaseLimitjest oznaczony jako zwracający plik Promise.

Wpisz alias

Aliasy typów to jeden z moich ulubionych sposobów używania typów statycznych. Umożliwiają one wykorzystanie istniejących typów (liczba, ciąg itp.) Do tworzenia nowych typów:

Powyżej utworzyłem nowy typ o nazwie, PaymentMethodktóry ma właściwości, które składają się z typów numberi string.

Teraz, jeśli chcesz użyć tego PaymentMethodtypu, możesz:

Możesz również utworzyć aliasy typów dla dowolnego prymitywu, zawijając typ bazowy wewnątrz innego typu. Na przykład, jeśli chcesz wpisać alias a Namei EmailAddress:

Dzięki temu masz wskazując, że Namei Emailsą różne rzeczy, a nie tylko struny. Ponieważ imię i nazwisko oraz adres e-mail nie są tak naprawdę zamienne, zapobiega to przypadkowemu pomieszaniu ich.

Generics

Typy generyczne są sposobem na abstrakcję samych typów. Co to znaczy?

Spójrzmy:

Stworzyłem abstrakcję dla typu T. Teraz możesz użyć dowolnego typu, który chcesz reprezentować T. Dla numberT, Tbył typu number. Tymczasem dla arrayTT był w typieArrayer>.

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