Angular 2: чому на TypeScript?

Здрастуйте всі, хто не забуває заглядати у наш блог і традиційно неробочий час доби!

Давним-давно в нашій публікації від 13 листопада 2015 року ви переконали нас дочекатися допиливания Angular 2 і видати про нього книгу. Ми збираємося серйозно взятися за такий проект найближчим часом, а поки пропонуємо почитати розгорнуту відповідь на питання, винесене в заголовок цього поста.

Angular 2 написаний на мові TypeScript. У цій статті я розповім, чому було прийнято таке рішення. Крім того, поділюся власним досвідом роботи з TypeScript; як на ньому писати і рефакторіть код.

Мені TypeScript за смаком, а вам – можливо, і немає.

Так, Angular 2 написаний на TypeScript, але додатка в Angular 2 можна писати і без нього. Фреймворк відмінно взаємодіє з ES5, ES6 і Dart.

В TypeScript – відмінний інструментарій

Основне гідність TypeScript – це його оснащення. Тут забезпечується складне автозавершення, навігація і рефакторинг. У великих проектах без таких інструментів практично не обійтися. Своїми силами вічно не наважуєшся остаточно змінити код, вся база коду перебуває в сирому вигляді, і будь-який великий рефакторинг стає ризикованим і витратним.

TypeScript – не єдиний типізований мову, який компілюється в JavaScript. Є і інші мови з більш суворими системами типів, які теоретично повинні надавати абсолютно феноменальний інструментарій. Але на практиці у більшості з них ви не знайдете майже нічого крім компілятора. Справа в тому, що напрацювання багатого інструментарію повинна бути пріоритетною метою з самого першого дня – саме таку мету поставила перед собою команда TypeScript. Ось чому саме тут були створені мовні сервіси, які можуть використовуватися в редакторах для перевірки типів та автозаповнення. Якщо ви задавалися питанням, звідки таке безліч редакторів з відмінною підтримкою TypeScript – справа саме в мовних сервісах.

Інтелектуальне введення (intellisense) і найпростіший рефакторинг (наприклад, перейменування символу) докорінно змінюють процеси написання і, особливо, рефакторінгу коду. Хоча цей показник важко виміряти, мені здається, що рефакторинги, на які раніше витрачалося кілька днів, тепер робляться за кілька годин.

Так, TypeScript значно оптимізує редагування коду, але підготовка до розробки з ним складніше ніж, скажімо, взяти і закинути на сторінку скрипт ES5. Крім того, ви втрачаєте інструментів JavaScript для аналізу вихідного коду (напр., JSHint), але для них зазвичай є адекватні заміни.

TypeScript – це надмножество JavaScript

Оскільки TypeScript – це надмножество JavaScript, при міграції на цю мову не потрібно радикально переписувати код. Це можна робити поступово, модуль за модулем.
Просто беремо модуль, перейменовуємо в ньому файли
.js
на
.ts
, а потім поступово додаємо анотації типів. Закінчили з модулем – переходимо до наступного. Коли вся база коду буде типизирована, можна починати возитися з налаштуваннями компілятора, робити їх суворіше.

Весь процес може зайняти деякий час, але, коли ми робили міграцію Angular 2 на TypeScript, в процесі роботи вдалося не тільки розробити нові функції, але і пофіксити баги.

В TypeScript абстракції стають явними

Хороший дизайн – це грамотно певні інтерфейси. А висловити ідею інтерфейсу набагато простіше мовою, який підтримує інтерфейси.

Припустимо, є додаток для покупки книг. Книги в ньому купуються двома способами: зареєстрований користувач може робити це через графічний інтерфейс, а інші – через зовнішню систему, яка з'єднується з додатком через якийсь API.



Як бачите, роль обох класів в даному випадку – «покупець». Незважаючи на те, як важлива роль «покупець» в цьому додатку, в коді вона ніяк явно не виражена. Там немає файлу
purchaser.js
. Тому, хто-небудь може змінити код і навіть не помітити, що така роль існує.

function processPurchase(purchaser, details){ } 

class User { } 

class ExternalSystem { }


Якщо просто переглянути код, то складно сказати, які об'єкти можуть виступати в ролі покупця. Напевно ми не знаємо, і наші інструменти не особливо нам в цьому допоможуть. Таку інформацію доводиться виборювати вручну – а це справа повільне, обертаються помилками.

А ось версія, в якій ми визначаємо інтерфейс
Purchaser
.

interface Purchaser {id: int; bankAccount: Account;} 

class User implements Purchaser {} 

class ExternalSystem implements Purchaser {}


У типизированной версії чітко зазначено, що у нас є інтерфейс
Purchaser
, а класи
User
та
ExternalSystem
його реалізують. Отже, інтерфейси TypeScript дозволяють визначати абстракції/протоколи/ролі.

Важливо розуміти, що TypeScript не змушує нас додавати зайві абстракції. Абстракція «Покупець» є і в коді JavaScript, просто вона там явно не визначена.

статично типизированном мовою кордону між підсистемами визначаються за допомогою інтерфейсів. Оскільки в JavaScript немає інтерфейсів, позначити кордону на чистому JavaScript складніше. Якщо кордони для розробника не очевидні, то він залежить від конкретних типів, а не від абстрактних інтерфейсів, що провокує сильне зв'язування.

З досвіду роботи з Angular 2 до і після переходу на TypeScript таке переконання лише зміцнилося. Визначаючи інтерфейс, я змушений продумувати кордону API, це допомагає мені окреслювати публічні інтерфейси підсистем і відразу виявляти зв'язування, якщо воно випадково виникне.

З TypeScript простіше читати і розуміти код

Так, я в курсі, що на перший погляд так не здається. Тоді розглянемо приклад. Візьмемо функцію
jQuery.ajax()
. Яка інформація зрозуміла з її сигнатури?

Все, що можна сказати напевно – ця функція приймає два аргументи. Типи можна спробувати вгадати. Можливо, спочатку йде рядок, а за нею – конфігураційний об'єкт. Але це всього лише версія, можливо, ми помиляємося. Не уявляємо, які опції можуть бути в об'єкті налаштувань (ні їхніх імен, ні типів), не знаємо, що повертає ця функція.

Невідомо, як викликати цю функцію, треба звіритися з вихідним кодом або з документацією. Звірятися з вихідним кодом – не кращий варіант; що користі в функціях і класах, якщо не зрозуміло, як вони реалізовані. Іншими словами, потрібно спиратися на їх інтерфейси, а не на реалізацію. Можна перевіряти документацію, але розробники підтвердять, що це невдячна праця – на перевірку витрачається час, а сама документація часто вже неактуальна.

Отже, прочитати
jQuery.ajax(url, settings)
просто, але, щоб зрозуміти, як викликати цю функцію, потрібно вчитатися на її реалізацію, або в документацію.

А тепер порівняйте з типизированной версією.

ajax(url: string, settings?: JQueryAjaxSettings): JQueryXHR; 

interface JQueryAjaxSettings { 
async?: boolean; 
cache?: boolean; 
contentType?: any; 
headers?: { [key: string]: any; }; 
//... 
} 

interface JQueryXHR { 
responseJSON?: any; //... 
}


Ця версія набагато інформативніше:

  • Перший аргумент цієї функції – рядок.
  • Аргумент
    settings
    є опціональним. Ми бачимо всі параметри, які можуть бути передані функції – не тільки їх імена, але й типи.
  • Функція повертає об'єкт
    JQueryXHR
    , ми бачимо його властивості і функції.


Типизированная сигнатура виразно довше нетипизированной, але
:string
,
:JQueryAjaxSettings
та
JQueryXHR
– не сміття. Це важлива «документація», завдяки якій код простіше сприймається. Можна зрозуміти код значно глибше, не вдаючись у реалізацію або читання документів. Особисто я читаю типізований код швидше, тому що типи – це контекст, допомагає розуміти код. Але, якщо хтось з читачів знайде дослідження про те, як типи впливають на читабельність – залиште, будь ласка, посилання в коментарі.

Одне з важливих відмінностей між TypeScript та багатьма іншими мовами, компилируемыми в JavaScript – в тому, що анотації типів факультативні, і jQuery.ajax(url, settings) – це самий справжній валідний TypeScript. Отже, типи TypeScript можна порівняти скоріше не з вимикачем, а з регулювальним диском. Якщо ви вважаєте, що код тривіальний, і його цілком можна читати без анотацій типів – не використовуйте їх. Застосовуйте типи, тільки коли вони приносять користь.

TypeScript обмежує виразність?

Інструментарій в мовах з динамічною типізацією – так собі, але вони пластичніше і виразніше. Думаю, з TypeScript ваш код стане неповороткішим, але в значно меншій мірі, ніж може здатися. Зараз поясню. Припустимо, я використовую ImmutableJS, щоб визначити запис
Person
.

const PersonRecord = Record({name:null, age:null}); 

function createPerson(name, age) { 
return new PersonRecord({name, age}); 
} 

const p = createPerson("Jim", 44); 

expect(p.name).toEqual("Jim");


Як типізувати такий запис? Для початку визначимо інтерфейс під назвою Person:

interface Person { name: string, age: number };


Якщо намагаємося зробити так:

function createPerson(name: string, age: number): Person { 
return new PersonRecord({name, age}); 
}


то компілятор TypeScript лається. Він не знає, що PersonRecord насправді сумісний з Person, оскільки PersonRecord створювався рефлексивно. Деякі читачі, знайомі з ФП, можуть сказати: «Ах, якщо б у TypeScript були залежні типи!» Але тут їх немає. Система типів TypeScript не сама просунута. Але наша мета інша: не довести, що програма на 100% правильна, а надати вам більш детальну інформацію та більше якісний інструментарій. Тому цілком можна зрізати кути, якщо система типів не дуже гнучка.

Створену запис можна запросто призвести, ось так:

function createPerson(name: string, age: number): Person { 
return <any>new PersonRecord({name, age}); 
}


Типізований приклад:

interface Person { name: string, age: number }; 

const PersonRecord = Record({name:null, age:null}); 

function createPerson(name: string, age: number): Person { 
return <any>new PersonRecord({name, age}); 
} 

const p = createPerson("Jim", 44); 

expect(p.name).toEqual("Jim");


Це працює, тому що система типів структурна. Якщо у створеному об'єкті є потрібні поля — ім'я та вік — то все в порядку.

Необхідно звикнути, що при роботі з TypeScript «зрізати кути» нормально. Тільки тоді вам буде приємно мати справу з цією мовою. Наприклад, не намагайтеся додавати типи в якийсь химерний код для метапрограммирования – швидше за все, статично це висловити просто не зможете. Поколдуйте з цим кодом і накажіть системі перевірки типів, щоб ігнорувала химерну частина. У такому разі виразності ви майже не втратите, але основна маса коду залишиться зручній для обробки і аналізу.

Ситуація нагадує спробу забезпечити стовідсоткове покриття модульними тестами. 95% зазвичай робиться без проблем, а от домогтися 100% — вже завдання, причому таке покриття може негативно позначитися на архітектурі всього програми.

При опціональній системі типів також зберігається звичний для JavaScript цикл розробки. Великі фрагменти бази коду, можливо, виявляться «розбиті», але ви все одно зможете їх запускати. TypeScript так і буде генерувати JavaScript, навіть якщо система перевірки типів залишиться незадоволеною. В ході розробки це виключно зручно.

Чому TypeScript?

Сьогодні у фронтендеров багатий вибір інструментів для розробки: ES5, ES6 (Babel), TypeScript, Dart, PureScript, Elm, т. д. Навіщо ж TypeScript?

Почнемо з ES5. У ES5 є одна серйозна перевага над TypeScript: тут не потрібно транспилятор. Тому всю розробку організувати просто. Не доводиться налаштовувати file watcher'и, транспилировать код, генерувати карти коду. Все просто працює.

У ES6 потрібен транспилятор, тому сама збірка буде організована приблизно як в TypeScript. Але це стандарт, який означає, що всі до одного редактори або складальні інструменти або підтримують ES6, або будуть підтримувати. Зараз у більшості редакторів TypeScript вже відмінно підтримується.

Elm і PureScript – красиві мови з потужними системами типів, які можуть дати вашої програми набагато більше, ніж TypeScript. Код на Elm і PureScript може виходити набагато лаконічніше, ніж на ES5.

У кожного з цих варіантів є свої переваги і недоліки, але мені здається, що TypeScript – золота середина, і відмінно підійде для більшості проектів. TypeScript володіє 95% достоїнств хороших статично типізованих мов, і привносить ці достоїнства в екосистему JavaScript. Відчуття майже таке ж, як ніби пишеш в ES6: користуєшся все тієї ж стандартною бібліотекою, тими ж сторонніми бібліотеками, ідіомами і багатьма звичними інструментами (наприклад, розділ «Розробка» у Chrome). Ви отримуєте масу всього смачного, не залишаючи звичної екосистеми JavaScript.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.