Защо да използвам статични типове в JavaScript? (Праймер от 4 части за статично писане с Flow)

Като разработчик на JavaScript можете да кодирате през целия ден, без да срещате статични типове. Тогава защо да си правим труда да научавам за тях?

Оказва се, че типовете учене не са просто упражнения за разширяване на ума. Ако сте готови да инвестирате малко време в изучаване на предимствата, недостатъците и случаите на използване на статичните типове, това може да помогне изключително на вашето програмиране.

Интересувате ли се? Е, имате късмет - за това е останалата част от тази поредица от четири части.

Първо, определение

Най-бързият начин да разберете статичните типове е да ги сравните с динамичните типове. Език със статични типове се нарича статически типизиран език . От друга страна, език с динамични типове се нарича динамично типизиран език.

Основната разлика е, че статично типизираните езици извършват проверка на типа по време на компилация , докато динамично типизираните езици извършват проверка на типа по време на изпълнение .

Това ви оставя още една концепция, с която да се справите: какво означава „ проверка на типа“ ?

За да обясним, нека разгледаме типовете в Java спрямо Javascript.

„Типове“ се отнася до типа данни, които се дефинират.

Например в Java, ако дефинирате booleanкато:

boolean result = true;

Това има правилен тип, тъй като booleanанотацията съответства на дадената стойност result, за разлика от цяло число или нещо друго.

От друга страна, ако сте се опитали да декларирате:

boolean result = 123;

... това не би успяло да се компилира, защото има неправилен тип. Той изрично маркира resultкато a boolean, но след това го определя като цяло число 123.

JavaScript и други динамично типизирани езици имат различен подход, позволявайки на контекста да установи какъв тип данни се дефинират:

var result = true;

Накратко: статично типизираните езици изискват да декларирате типовете данни на вашите конструкции, преди да можете да ги използвате. Динамично типизираните езици не го правят. JavaScript предполага типа данни, докато Java го заявява направо.

Така че, както можете да видите, типовете ви позволяват да посочите програмни инварианти или логическите твърдения и условия, при които програмата ще изпълнява.

Проверката на типа проверява и налага, че типът на конструкция (константа, булева стойност, число, променлива, масив, обект) съвпада с инвариант, който сте посочили. Можете например да посочите, че „тази функция винаги връща низ.“ Когато програмата се изпълни, можете спокойно да предположите, че тя ще върне низ.

Разликите между статичната проверка на типа и динамичната проверка на типа имат най-голямо значение, когато възникне грешка в типа. В статично типизиран език грешки в типа възникват по време на стъпката на компилиране, т.е. по време на компилиране. В динамично типизираните езици грешките възникват само след изпълнението на програмата. Тоест по време на изпълнение .

Това означава, че програма, написана на динамично въведен език (като JavaScript или Python), може да се компилира, дори ако съдържа грешки в типа, които иначе биха попречили на скрипта да работи правилно.

От друга страна, ако програма, написана на статично типизиран език (като Scala или C ++), съдържа грешки в типа, тя няма да може да се компилира, докато грешките не бъдат отстранени.

Нова ера на JavaScript

Тъй като JavaScript е динамично типизиран език, можете да декларирате променливи, функции, обекти и каквото и да е, без да декларирате типа.

Удобен, но не винаги идеален. Ето защо наскоро се включиха инструменти като Flow и TypeScript, за да дадат на разработчиците на JavaScript опцията * да използват статични типове.

Flow е библиотека за проверка на статичен тип с отворен код, разработена и пусната от Facebook, която ви позволява постепенно да добавяте типове към вашия JavaScript код.

TypeScript , от друга страна, е супермножество , което се компилира до JavaScript - въпреки че се чувства почти като нов статично типизиран език сам по себе си. Въпреки това изглежда и се чувства много подобно на JavaScript и не е трудно да се вземе.

И в двата случая, когато искате да използвате типове, изрично казвате на инструмента за това кои файлове да се проверят. За TypeScript правите това като пишете файлове с .tsразширението вместо .js. За Flow включвате коментар отгоре на файла с@flow

След като декларирате, че искате да проверите даден файл, можете да използвате съответния синтаксис за дефиниране на типове. Една разлика, която трябва да се направи между двата инструмента е, че Flow е тип „проверка“, а не компилатор. TypeScript, от друга страна, е компилатор.

Наистина вярвам, че инструменти като Flow и TypeScript представляват промяна в поколенията и напредък за JavaScript.

Лично аз научих толкова много, използвайки типове в ежедневието си. Ето защо се надявам да се присъедините към мен в това кратко и сладко пътешествие в статични типове.

Останалата част от тази публикация от 4 части ще обхване:

Част I. Кратко въведение в синтаксиса и езика на Flow

Части II и III. Предимства и недостатъци на статичните типове (с подробни примери за преминаване)

Част IV. Трябва ли да използвате статични типове в JavaScript или не?

Имайте предвид, че избрах Flow над TypeScript в примерите в тази публикация поради познаването ми с него. За ваши собствени цели, моля, направете проучване и изберете подходящия инструмент за вас. TypeScript също е фантастичен.

Без повече шум, нека започнем!

Част 1: Кратко въведение в синтаксиса и езика на Flow

За да разберете предимствата и недостатъците на статичните типове, първо трябва да получите основно разбиране на синтаксиса за статични типове, използвайки Flow. Ако никога преди не сте работили на статично написан език, синтаксисът може да отнеме малко време, за да свикнете.

Нека започнем с проучване как да добавяме типове към примитиви на JavaScript, както и конструкции като масиви, обекти, функции и др.

булев

Това описва boolean(вярна или невярна) стойност в JavaScript.

Забележете, че когато искате да посочите тип, синтаксисът, който използвате, е:

номер

Това описва номер на IEEE 754 с плаваща запетая. За разлика от много други езици за програмиране, JavaScript не дефинира различни типове числа (като цели числа, къси, дълги и плаващи точки). Вместо това числата винаги се съхраняват като числа с плаваща запетая с двойна точност. Следователно ви е необходим само един тип число, за да дефинирате произволно число.

numberвключва Infinityи NaN.

низ

Това описва низ.

нула

Това описва типа nullданни в JavaScript.

нищожен

Това описва типа undefinedданни в JavaScript.

Имайте предвид, че nullи undefinedсе третират по различен начин. Ако сте се опитали да направите:

Flow ще изведе грешка, тъй като voidсе предполага, че типът е от тип, undefinedкойто не е същият като типа null.

Масив

Описва масив на JavaScript. Използвате синтаксиса Array<; T>, за да опишете масив, чиито елементи са от някакъв тип T.

Забележете как замених Tс string, което означава, че декларирам messagesкато масив от низове.

Обект

Това описва обект на JavaScript. Има няколко различни начина за добавяне на типове към обекти.

Можете да добавите типове, за да опишете формата на обект:

Можете да дефинирате обекти като карти, където присвоявате низ на някаква стойност:

Можете също така да дефинирате обект като Objectтип:

Този последен подход ни позволява да зададем какъвто и да е ключ и стойност на вашия обект без ограничения, така че всъщност не добавя много стойност по отношение на проверката на типа.

всякакви

Това може да представлява буквално всеки тип. На anyвид е ефективно непокътнати, така че трябва да се опита да избегне да го използвате, освен ако не е абсолютно необходимо (като, когато трябва да се откажете от тип проверка или нужда вариант за изход).

Една ситуация, за която може да ви се anyстори полезна, е когато използвате външна библиотека, която разширява прототипите на друга система (като Object.prototype).

Например, ако използвате библиотека, която разширява Object.prototype със doSomethingсвойство:

Може да получите грешка:

За да заобиколите това, можете да използвате any:

Функции

Най-често срещаният начин за добавяне на типове към функциите е добавянето на типове към входните аргументи и (когато е уместно) връщаната стойност:

Можете дори да добавяте типове към асинхронни функции (вижте по-долу) и генератори:

Забележете как вторият ни параметър getPurchaseLimitсе коментира като функция, която връща a Promise. И amountExceedsPurchaseLimitсе коментира като също връща a Promise.

Въведете псевдоним

Типичното псевдоним е един от любимите ми начини за използване на статични типове. Те ви позволяват да използвате съществуващи типове (число, низ и т.н.) за съставяне на нови типове:

По-горе създадох нов тип, наречен, PaymentMethodкойто има свойства, които се състоят от numberи stringтипове.

Сега, ако искате да използвате PaymentMethodтипа, можете да направите:

Можете също така да създадете псевдоними на типове за всеки примитив, като увиете основния тип в друг тип. Например, ако искате да въведете псевдоним a Nameи EmailAddress:

Правейки това, вие посочвате това Nameи Emailса различни неща, а не само низове. Тъй като име и имейл всъщност не са взаимозаменяеми, това не ви позволява случайно да ги смесвате.

Дженерици

Дженериците са начин за абстрахиране над самите типове. Какво означава това?

Нека да разгледаме:

Създадох абстракция за типа T. Сега можете да използвате какъвто и да е тип, който искате да представите T. За numberT, Tе от тип number. Междувременно за arrayT, Т беше от типArrayer>.

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