Навіщо використовувати статичні типи в 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(true або false) у JavaScript.

Зверніть увагу, що коли ви хочете вказати тип, ви використовуєте синтаксис:

номер

Це описує номер із плаваючою комою IEEE 754. На відміну від багатьох інших мов програмування, JavaScript не визначає різні типи чисел (наприклад, цілі числа, короткі, довгі та плаваючі крапки). Натомість числа завжди зберігаються як числа з плаваючою комою з подвійною точністю. Отже, для визначення будь-якого числа вам потрібен лише один тип числа.

numberвключає Infinityі NaN.

рядок

Це описує рядок.

нуль

Це описує nullтип даних у JavaScript.

порожнеча

Це описує undefinedтип даних у JavaScript.

Зауважте, що nullдо undefinedних по-різному ставляться. Якщо ви намагалися зробити:

Потік видасть помилку, оскільки 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