TypeScript за 30 хвилин

04 серпня 2016 10:46 OlegWock 595 4

Сьогодні ми розглянемо TypeScript — мову програмування, що компілюється в JavaScript і розроблена спеціально для великих додатків. Вона багато перейняла від таких мов як Java та C#, які є більш дисциплінованими та строгими, ніж динамічно типізований JS.

Ця стаття розрахована на людей, що вже добре знайомі з JS, але хочуть розібратися і з TypeScript також. Тут зібрані описи найважливіших функція та особливостей мови, і все це доповнено купою прикладів. Тож давайте почнемо!


Переваги TypeScript

JS — гарна мова, і в вас може виникнути думка "А я дійсно повинен вивчити ще й TypeScript?". Взагалі, це не обов'язково, багато людей чудово живуть і без нього, але його використання має свої переваги:

  • Із-за статичної типізації код на TypeScript більш передбачуваний і тому його легко дебажити.
  • Завдяки гарній підтримці ООП, модулів та просторів імен, проект, навіть при великому об'ємі, буде впорядкованим.
  • Компілятор на стадії збирання знаходить багато помилок, ще до того як вони потраплять в рантайм і зламають щось.
  • Нова версія Angular (Angular 2), що має скоро вийти, написана на TypeScript (далі — TS) і рекомендує його як мову розробки.

Для багатьох людей останній пункт є головним для вивчення TS. Angular 2 — це один з найпопулярніших фреймворків. І не дивлячись на те, що він також підтримує JS, все ж більшість туторіалів написана для TS. І з розширенням громади Angular 2 буде розширюватися громада користувачів TS. Ось статистика на Google Trends
statistic


Встановлення TypeScript

Найлегше встановити TS через npm. Це пакетний менеджер Node.js і для його використання спершу його потрібно встановити. Якщо ви цього ще не зробили: Node.js. А ми продовжимо. Давайте інсталюємо пакет typescript глобально. На лінуксі, можливо, треба буде виконати з правами супер-користувача, залежить від ваших налаштувань. — пер.

npm install -g typescript

Після інсталювання перевіримо версію:

tsc -v
Version 1.8.10

Текстові редактори

TS - проект з відкритим сирцевим кодом, але він розроблений і підтримується головним чином Microsoft, і тому спочатку він підтримувався тільки Visual Studio. Але зараз ці часи минули і вже більшість редакторів та IDE підтримують його: нативно чи з допомогою плагінів.

  • Visual Studio Code — ще один редактор від Microsoft, легковісний та відкритий. Підтримка TS вбудована.
  • Офіційний плагін для Sublime Text 2/3
  • Остання версія WebStorm (як і інші IDE від JetBrains) має вбудовану підтримку TS.
  • Список плагінів для інших редакторів, таких як Vim, Emacs, Atom, etc.

Компіляція в JavaScript

Код на TS пишуть в файлах .ts (або .tsx для JSX), які не можуть виконуватися в браузері, а потребують компіляції в JS. Зробити це можна різними шляхами:

  • З терміналу, використовуючи утиліту tsc
  • З Visual Studio або інших IDE, що мають таку функцію
  • Використовуючи, збиральники проектів (такі як Grunt та Gulp)

Ми будемо використовувати найпростіший, перший спосіб. Наступна команда компілює файл main.ts в main.js, при чому, якщо останній вже існував, то він буде перезаписаний.

tsc main.ts

А ось так можна скомпілювати декілька файлів: за повним переліком, або за маскою:

# Результати будуть в різних файлах: main.js worker.js.
tsc main.ts worker.ts
# Компілює всі .ts-файли в даній теці. Але робить це НЕ рекурсивно
tsc *.ts

Також для того, щоб кожен раз не виконувати потрібну команду, можна передати параметр --watch і утиліта сама буде слідкувати за файлами і при зміні перекомпілює потрібні.

tsc main.ts --watch

Більш досвідчені користувачі TS можуть створити файл tsconfig.json, в якому зберігаються налаштування. Це дуже корисно коли ви працюєте над великим проектом з купою файлів. Більше про нього можна почитати тут.


Статична типізація

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

Ось приклад. Будь-яка змінна, функція, аргумент має свій тип, який потрібно вказувати.

var burger: string = 'hamburger',     // String 
    calories: number = 300,           // Numeric
    tasty: boolean = true;            // Boolean
// Також можна писати так
// var burger = 'hamburger';
// Функція приймає стрічку та число
//Функція нічого не повертає, тому має тип void.
function speak(food: string, energy: number): void {
  console.log("Our " + food + " has " + energy + " calories.");
}
speak(burger, calories);

Через те, що TypeScript компілюється в JavaScript, в кінці всі типи зникають:

// JavaScript прикладу вище
var burger = 'hamburger',
    calories = 300, 
    tasty = true; 
function speak(food, energy) {
    console.log("Our " + food + " has " + energy + " calories.");
}
speak(burger, calories);

Якщо ми спробуємо зробити щось неправильно, tsc вкаже нам на це.

var tasty: boolean = "I haven't tried it yet";
main.ts(1,5): error TS2322: Type 'string' is not assignable to type 'boolean'.

Також, при неправильних аргументах теж буде помилка:

function speak(food: string, energy: number): void{
  console.log("Our " + food + " has " + energy + " calories.");
}
// Аргументи не відповідають потрібним типам
speak("tripple cheesburger", "a ton of");
main.ts(5,30): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

Ось список найуживаніших типів даних:

  • Number — всі числа, і цілі, і з комою.
  • String — тут зберігається текст. Як і в звичайному JS підтримуються обидва вида лапок (' та ").
  • Boolean — true або false, використання 0 чи 1 спричинить помилку.
  • Any — змінна такого типу може зберігати будь яке значення.
  • Arrays — масиви. Мають два синтаксиса: my_arr: number[]; та my_arr: Array<number>.
  • Void — використовується лише для функцій і вказує, що вона не повертає ніякого значення.

Повний список типів перелічений в офіційній документації.


Інтерфейси

Інтерфейси використовуються для перевірки типів і являють собою об'єкто-подібний API. Оголосивши інтерфейс, ми можемо дати змінним всередині зручні імена і бути впевненими, що вони завжди будуть знаходитися поряд. Після компіляції в JS інтерфейси повністю зникають — вони створені лише для полегшення розробки.

Ось приклад, що ілюструє їх використання:

// Оголошення нашого інтерфейсу
interface Food {
    name: string;
    calories: number;
}
// Ми явно вказуємо, який об'єкт очікуємо прийняти
// також ми можемо бути впевненими, що всі властивості в об'єкті будуть присутні (і міститимуть правильні значення)
function speak(food: Food): void{
  console.log("Our " + food.name + " has " + food.calories + " calories.");
}
// Ми створюємо об'єкт, що відповідає інтерфейсу Food. Зауважте, тип присвоюється автоматично
var ice_cream = {
  name: "ice cream", 
  calories: 200
}
speak(ice_cream);

Порядок оголошення властивостей не важливий. Головне щоб вони були присутні і були вірного типу. Якщо щось буде не так — компілятор видасть помилку.

interface Food {
    name: string;
    calories: number;
}
function speak(food: Food): void{
  console.log("Our " + food.name + " has " + food.calories + " grams.");
}
// Помилка в назві властивості name.
var ice_cream = {
  nmae: "ice cream", 
  calories: 200
}
speak(ice_cream);
main.ts(16,7): error TS2345: Argument of type '{ nmae: string; calories: number; } 
is not assignable to parameter of type 'Food'. 
Property 'name' is missing in type '{ nmae: string; calories: number; }'.

Це гайд для початківців, тому ми не будемо розглядати всі тонкощі. Додаткова документація тут.


Класи

При розробці великих додатків більшість розробників віддає перевагу об'єктивно-орієнтованому підходу. TS надає нам систему класів дуже схожу до тієї, що в C# чи Java. В її можливості входять: абстракція класів, імплементація інтерфейсів, сеттери/геттери та багато іншого.

Також потрібно загадати, що в новому стандарті ES6 також додали нативну підтримку класів і тепер ними можна користуватися і без TS. Але в них є свої відмінності, TypeScript більш суворий.

Якщо продовжити тему їжі, то ось приклад класу на TS:

class Menu {
  // Наші властивості
  // Без явного вказання вони публічні, але можуть бути приватними та захищеними (protected)
  items: Array<string>;  // Пункти меню, масив стрічок.
  pages: number;         // Скільки сторінок в нашому меню.
  // Простий конструктор. 
  constructor(item_list: Array<string>, total_pages: number) {
    // Ключове слово this обов'язкове.
    this.items = item_list;    
    this.pages = total_pages;
  }
  // Методи
  list(): void {
    console.log("Our menu for today:");
    for(var i=0; i<this.items.length; i++) {
      console.log(this.items[i]);
    }
  }
} 
// Створення нового об'єкту типу Menu.
var sundayMenu = new Menu(["pancakes","waffles","orange juice"], 1);
// Викликаємо метод list().
sundayMenu.list();

Якщо ви писали на C# чи Java, синтаксис повинен бути вам знайомий. А ось так організується наслідування:

class HappyMeal extends Menu {
  // Властивості успадковуються
  // Оголошуємо новий конструктор.
  constructor(item_list: Array<string>, total_pages: number) {
    // Коли ми хочемо ініціалізувати батьківський конструктор, то ми використовуємо функцію super
    super(item_list, total_pages);
  }
  // Як і властивості, методи також успадковуються. Але їх можна перевизначити
  list(): void{
    console.log("Our special menu for children:");
    for(var i=0; i<this.items.length; i++) {
      console.log(this.items[i]);
    }
  }
}
// Створюємо новий об'єкт
var menu_for_children = new HappyMeal(["candy","drink","toy"], 1);
// Тепер перед меню буде спеціальне повідомлення.
menu_for_children.list();

А тут можна знайти більше детальний гайд по ООП в TS.


Примітиви

Примітиви — це шаблони, що дозволяють функції працювати з різними типами. Створення універсальних компонентів за допомогою примітивів краще, ніж використання типу any, так як примітиви зберігають тип змінної, що в них зараз зберігається.

Простий приклад функції яка приймає аргумент і повертає його ж, але в масиві.

// <T> після назви вказує, що це функція-примітив.
// Коли ми її викликаємо, всі T заміняються на відповідні типи.
// Приймає один аргумент типу T,
// Повертає масив об'єктів типу T.
function genericFunc<T>(argument: T): T[] {
  var arrayOfT: T[] = [];    // створення пустого масиву типу T.
  arrayOfT.push(argument);   // Додаємо елемент, тепер arrayOfT = [argument].
  return arrayOfT;
}
var arrayFromString = genericFunc<string>("beep");
console.log(arrayFromString[0]);         // "beep"
console.log(typeof arrayFromString[0])   // String
var arrayFromNumber = genericFunc(42);
console.log(arrayFromNumber[0]);         // 42
console.log(typeof arrayFromNumber[0])   // number

В першому виклику ми явно вказали тип примітиву, це не обов'язково, так як компілятор може зробити це сам. Але це все ж вважається гарним тоном, і я рекомендую завжди вказувати тип. Це дуже допомагає в майбутньому, особливо у великому проекті. Документація TS містить дуже багато корисної інформації по примітивах.


Модулі

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

TypeScript вводить синтаксис для експортування та імпортування модулів, але не може створити фактичний зв'язок між файлами. Реалізація цього механізму покладається на сторонні бібліотеки: require.js для браузерів та CommonJS для Node.js. Давайте розглянемо простий приклад модулів з використанням require.js:

Ми маємо два файли. Один експортує функцію, а інший її імпортує і викликає.

exporter.ts

var sayHi = function(): void {
    console.log("Hello!");
}
export = sayHi;

importer.ts

import sayHi = require('./exporter');
sayHi();

Тепер потрібно завантажити require.js та підключити за допомогою тегу script. Ось тут описано як це зробити. Останнім кроком буде скомпілювати наші два файли. Щоб підключити require.js (також відомий як AMD) потрібно вказати це аргументом:

tsc --module amd *.ts

Більше інформації про модулі у офіційній документації.


Сторонні файли-декларації

Коли ви використовуєте бібліотеку, що була написана для JS, спершу потрібно написати файл-декларацію, що зробить цю бібліотеку сумісною з TS. Файли-декларації мають розширення .d.ts і зберігають різну інформацію про API бібліотеки.

Зазвичай ці файли пишуть вручну, але є великий шанс, що це вже зробили до вас. DefinitelyTyped це найбільше сховище таких файлів, що зберігає інформацію про тисячі бібліотек. Також є популярний модуль для Node.js для керування деклараціями: Typings.

Якщо ви все ж не знайшли потрібної декларації — можете спробувати написати її самостійно по цьому гайду.


Майбутні особливості TypeScript 2.0

TypeScript все ще знаходиться в стадії активної розробки та розвитку. На час написання туторіалу останньою LTS (тобто стабільною) версією була 1.8.10, але вже є бета-реліз TypeScript 2.0. Він доступний для публічного тестування і ви можете встановити його собі:

npm install -g typescript@beta

А одними з його ключових особливостей є:

  • Non-nullable типи даних, що не дозволяють зберігати у змінних значення null чи undefined.
  • Покращена система отримання декларацій безпосередньо через npm install.
  • Покращення аналізатора, який буде ловити помилки, що раніше не бачив компілятор.
  • Деякі зміни в синтаксисі імпорту та експорту.

Також окремо слід відмітити підтримку асинхронного програмування з async/await. Вона буде реалізована в майбутній версії 2.1.

Що почитати далі

Перш за все, потрібно читати документацію. Ця стаття — лише введення в великий світ TypeScript, і вона не покриває навіть верхівку айсбергу. Ось дуже корисні фічі TS, які ми не згадували в статті:

  • Namespaces – тут.
  • Enums – тут.
  • Прогресивні типи даних та захист – тут.
  • Пишемо JSX на TypeScript – тут.

Висновок

Сподіваюсь, своїм туторіалом я зацікавив вас і ви ближче познайомитесь з такою прекрасною мовою як TypeScript, адже у неї є багато переваг!

Джерело перекладу

595 4

Схожі матеріали:

Коментарі:

Костя Третяк

05 Сер 2016 22:21
Цей коментар прихований автором

Костя Третяк

06 Сер 2016 06:53

На скільки я зрозумів, автор перекладає identity function як функції примітивів. Хіба не краще це перекласти як функції з ідентичністю або ідентичні функції.

Я кажу нові поняття, які можливо "вухо ріжуть", але, по-перше, в оригіналі так говориться, по-друге, це і по суті не зрівняти з цим розпливчатим терміном "примітиви".

OlegWock

07 Сер 2016 00:17

В оригіналі це було "generics", а не "identity function". І так, я шукав більш підхожий термін (під анонсом посту в вк є невеличке обговорення), але не знайшов. І функції з ідентичністю теж якось не дуже звучать

Костя Третяк

07 Сер 2016 10:16

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

Авторизуйтесь, щоб залишити коментар.