Сегодня мы рассмотрим TypeScript, - язык компиляции в JavaScript, предназначенный для разработчиков, которые строят большие и сложные приложения. Он наследует многие концепции программирования таких языков, как C# и Java, которые добавляют больше порядка «расслабленному» и свободному JavaScript.
Эта статья предназначена для людей, которые хорошо знают JavaScript, но мало знакомы с TypeScript. Мы рассмотрим большую часть основных функций, снабдив их примерами с комментариями.
Преимущества TypeScript
JavaScript довольно неплохой язык программирования и вы можете задаться вопросом: «Действительно ли мне нужен TypeScript»? Технически, вам не нужно изучать его, чтобы быть хорошим разработчиком. Тем не менее, работа с TypeScript имеет свои преимущества:
- Благодаря статической типизации код, написанный в TypeScript более предсказуем и, как правило, легче в отладке.
- Упрощает организацию базового кода для очень больших и сложных программ благодаря модулям, пространствам имен и мощной поддержке ООП.
- Компиляция в TypeScript включает в себя этап перехода к JavaScript, который обнаруживает все ошибки до того, как код, их содержащий, выполнится.
- Фреймворк Angular 2 написан на TypeScript и рекомендуется разработчикам к использованию в их проектах.
Последний пункт является самым важным. Angular 2 - один из наиболее популярных на сегодняшний день фреймворков и хотя разработчики при работе с ним могут использовать обычный JavaScript, большинство учебников и примеров написаны на TS. Angular 2 расширяет свое сообщество и, естественно, что все больше и больше людей будет изучать TypeScript.
Рост популярности TypeScript, данные из Google Trends
Установка TypeScript
Для работы нам потребуется Node.js и npm для этого урока. С помощью команды ниже мы можем установить пакет TypeScript на глобальном уровне, что делает компилятор TS доступным во всех наших проектах:
npm install -g typescript
Попробуйте открыть терминал в любом месте и наберите tsc -v
чтобы убедиться, что npm правильно установлено.
tsc -v
Version 1.8.10
Текстовые редакторы с поддержкой TypeScript
TypeScript - проект с открытым исходным кодом, но разработан корпорацией Microsoft и изначально поддерживался лишь в платформе Visual Studio. В настоящее время существует масса текстовых редакторов и IDE, которые либо изначально, либо через плагины предлагают поддержку синтаксиса TypeScript, предложения автозаполнения, перехват ошибок и даже встроенные в компиляторы.
- Visual Studio Code – легкий, с открытым исходным кодом редактор от Microsoft со встроенной поддержкой TypeScript.
- Плагин для редактора Sublime Text.
- Последняя версия WebStorm обладает встроенной поддержкой TypeScript.
- Прочие, включая Vim, Atom, Emacs.
Компиляция TypeScript в JavaScript
TypeScript записывается в .ts файлы (или .tsx для JSX), которые не могут быть использованы непосредственно в браузере и должны быть переведены в vanilla .js. Этот процесс компиляции может быть сделан следующими способами:
- В терминале, используя ранее упомянутый инструмент командной строки
tsc
.
- Непосредственно в Visual Studio, IDE и других текстовых редакторах.
- Используя автоматизированные таск раннеры, например gulp.
Первый способ нам показался самым простым, поэтому мы и будем использовать его в нашем уроке. Команда, представленная ниже, принимает TypeScript-файл по имени main.ts
и переводит его в JavaScript-версию: main.js
. Если main.js
существует, - он будет перезаписан.
tsc main.ts
Мы также можем собрать несколько файлов одновременно, перечислив все из них, или применив символы подстановки:
# Скомпилируем файлы main.js и worker.js
tsc main.ts worker.ts
# Компилирует все .ts файлы в текущей папке,
# не работает рекурсивно
tsc *.ts
Мы можем также использовать опцию --watch
, чтобы автоматически скомпилировать TypeScript-файл, когда сделаны изменения:
# Инициализирует процесс, который
# будет сдедить за актуальностью main.js
tsc main.ts --watch
Более продвинутые пользователи могут также создавать файл конфигурации tsconfig.json, состоящий из различных параметров сборки. Файл конфигурации очень удобен при работе над крупными проектами с большим количеством .ts-файлов так как он несколько автоматизирует процесс. Подробнее о файле конфигурации можно узнать в документации по tsconfig.json.
Статическая типизация
Отличительной особенностью TypeScript является поддержка статической типизации. Это означает, что вы можете объявлять типы переменных и компилятор будет проверять правильность типов значений. Если объявления типа опущены, они будут автоматически определены из вашего кода. Любая переменная, аргумент функции или возвращаемое значение может иметь свой тип, определенный при инициализации:
var burger: string = 'гамбургер', // Строка
calories: number = 300, // Числовое значение
tasty: boolean = true; // Логическое значение
// В качестве альтернативы, вы можете опустить объявление типа:
// var burger = 'гамбургер';
// Функция ожидает строку и целое число.
// Она не возвращает ничего, поэтому сама функция пустая..
function speak(еда: string, энергия: number): void {
console.log("Ваш " +food+ " содержит " +energy+ " калорий");
}
speak(burger, calories);
Поскольку TypeScript компилируется в JavaScript, который не имеет ни малейшего представления о типах данных, они будут полностью удалены:
// JavaScript код из приведенного выше примера
var burger = 'гамбургер',
calories = 300,
tasty = true;
function speak(food, energy) {
console.log("Ваш " +food+ " содержит " +energy+ " калорий");
}
speak(burger, calories);
Однако, если мы попробуем сделать что-то неправильное, попытка компиляции tsc
выдаст предупреждение об ошибке в коде. Например:
// Данный тип является логическим, значение является строкой
var tasty: boolean = "Я его еще не пробовал";
main.ts(1,5): error TS2322: Тип 'Строка' не может быть назначен для типа 'Логическое значение'.
Он также предупреждает нас, если мы передаем неправильный аргумент функции:
function speak(food: string, energy: number): void{
console.log("Ваш " +food+ " содержит " +energy+ " калорий");
}
// Аргументы не соответствует параметрам функции.
speak("тройной чизбургер", "a ton of");
main.ts(5,30): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. (ошибка TS2345: Аргумент типа 'string' не может быть присвоен параметру типа 'number’.)
Вот некоторые из наиболее часто используемых типов данных:
- Number – Любые числовые значения представлены числовым типом. И нет никаких отдельных определений для целочисленных, дробных и прочих переменных.
- String – строки, таклй же текстовый тип как и в JavaScript, может быть окружен
'
(одинарные кавычки), или "
(двойные кавычки).
- Boolean – логическое значение
true
, или false
. Использование 0
, или 1
вызовет ошибку компиляции.
- Any – · Any – Такая переменная может иметь такой тип, как String, Number, или что-либо другое.
- Arrays – массивы, имеют два возможных синтаксиса:
my_arr: number[];
, или my_arr: Array<number>
.
- Void – Используется для функций, которые ничего не возвращают.
Чтобы увидеть список всех доступных типов, перейдите на официальную документацию по TypeScript.
Интерфейсы
Интерфейсы используются для того, чтобы проверить, подходят ли объекты под ту или иную структуру. С их помощью можно выделять целые комбинации переменных, которые точно будут использоваться вместе друг с другом. При переводе на JavaScript интерфейсы исчезают - их единственная цель состоит в том, чтобы помочь в стадии разработки.
В приведенном ниже примере мы определяем простой интерфейс, который проверит аргументы функции:
// Здесь мы определяем наш интерфейс Food, его свойства и типы этих свойств.
interface Food {
name: string;
calories: number;
}
// Делаем так, чтобы наша функция ожидала объекта, подходящего к интерфейсу Food.
// Таким образом, мы знаем, что нужные нам свойства будут всегда доступны
function speak(food: Food): void{
console.log("Ваш " +food.name+ " содержит " +food.calories+ " калорий");
}
// Мы определяем объект, у которого есть все свойства интерфейса Food.
// Обратите внимание на то, что типы будут выведены автоматически
var ice_cream = {
name: "мороженое",
calories: 200
}
speak(ice_cream);
Порядок свойств НЕ ИМЕЕТ значения, нужно лишь, чтобы они были верного типа. Если что-то отсутствует, имеет неправильный тип, или называется по-другому, - компилятор предупредит нас.
interface Food {
name: string;
calories: number;
}
function speak(food: Food): void{
console.log("Ваш " +food.name+ " содержит " +food.calories+ " калорий");
}
// Мы сделали осознанную ошибку и написали nmae вместо name
var ice_cream = {
nmae: "мороженое",
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; }'. ошибка TS2345: Аргумент типа '{ nmae: строка; calories: число; } не может быть присвоен параметру типа 'Food'. Свойство 'name' не найдено в типе '{ nmae: string; calories: number; }'.
Поскольку это руководство для начинающих, мы не будем углубляться в интерфейсы. Тем не менее, если вы хотите побольше узнать о них, загдяните в документацию по интерфейсам.
Классы
При создании крупномасштабных приложений, объектно-ориентированный стиль программирования является предпочтительным для многими разработчиков, особенно в таких языках, как Java или C#. TypeScript предлагает систему классов, которая очень похожа на ту, что используется в этих языках, в том числе наследование, абстрактные классы, реализации интерфейса, сеттеры/геттеры и многое другое.
Также стоит отметить, что с последнего обновления JavaScript (ECMAScript 2015), классы являются родными для vanilla JS и могут быть использована без TypeScript. Две эти реализации очень похожи, но имеют некоторые отличия.
Продолжая тему еды напишем простой класс на TypeScript:
class Menu {
// Наши свойства (по умолчанию они являются публичными,
// но также могут быть приватными или защищенными):
items: Array<string>; // Пункты меню, массив строк
pages: number; // Сколько страниц будет в меню, число
// Несложный конструктор
constructor(item_list: Array<string>, total_pages: number) {
// Это ключевое слово является обязательным
this.items = item_list;
this.pages = total_pages;
}
// Методы
list(): void {
console.log("Наше меню на сегодня:");
for(var i=0; i<this.items.length; i++) {
console.log(this.items[i]);
}
}
}
// Создаем новый экземпляр класса Menu
var sundayMenu = new Menu(["блины","вафли","апельсиновый сок"], 1);
// Вызываем метод list
sundayMenu.list();
Тот, кто работал с Java или C# заметит, что синтаксисы довольно похожи. То же самое касается наследования:
class HappyMeal extends Menu {
// Свойства наследуются
// Новый конструктор должен быть определен
constructor(item_list: Array<string>, total_pages: number) {
// Нам нужен точно такой же конструктор в качестве родительского
// класса (Menu). Чтобы автоматически скопировать его, мы можем
// вызвать super() - ссылку на конструктор родителя.
super(item_list, total_pages);
}
// Так же как и свойства, методы наследуются от родителя.
// Тем не менее, мы хотим переопределить функцию list(),
// поэтому сделаем это.
list(): void{
console.log("Наше специальное меню для детей:");
for(var i=0; i<this.items.length; i++) {
console.log(this.items[i]);
}
}
}
// Создать новый экземпляр класса HappyMeal
var menu_for_children = new HappyMeal(["конфеты","напитки","игрушки"], 1);
// В этот раз сообщение лога начнется с особого вступления
menu_for_children.list();
Для получения более подробной информации советуем вам изучить документацию по классам в TS.
Generics
Generics – это шаблоны, позволяющие одной и той же функции принимать аргументы разных типов. Лучше создавать компоненты в Generics, чем использовать тип данных any т.к. Generics сохраняет типы переменных, проходящих через него.
Простым примером мог бы стать скрипт, который получает аргумент и выводит массив, в котором он содержится.
// <T> после имени функции говорит о том, что эта функция относится к Generics.
// Когда мы вызываем функцию, все T заменяются на нужные типы.
// Получает аргумент типа T,
// Возвращает массив типа T.
function genericFunc<T>(argument: T): T[] {
var arrayOfT: T[] = []; // создает пустой массив типа T.
arrayOfT.push(argument); // теперь массив T = [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
Вызвав функцию в первый раз, мы вручную задали ей тип string (строка). Это не обязательная процедура, т.к. компилятор сам видит, какой аргумент передан, и может автоматически решить, какой тип ему лучше подходит, как и при втором вызове. Хотя это и не обязательно, но лучше каждый раз задавать тип самостоятельно, чтобы избежать возможных ошибок компилятора.
Документация по TypeScript включает в себя несколько продвинутых примеров, включая классы Generics, совмещая их с интерфейсами, а также многое другое. Вы можете прочитать об этом здесь.
Модули в TypeScript
Другим важным понятием при работе над большими приложениями является модульность. Разделение кода на множество мелких, повторно используемых компонентов помогает вашему проекту оставаться организованным и понятным, по сравнению одним файлом в 10000 строк.
TypeScript вводит синтаксис для экспорта и импорта модулей, но но не может обрабатывать связи между файлами. Для подключения внешних модулей TS полагается на сторонние библиотеки: require.js для браузерных приложений и CommonJS для Node.js. Давайте рассмотрим простой пример TypeScript-модулей с require.js:
У нас будет два файла. Один экспортирует функцию, а другой импортирует и вызывает ее.
exporter.ts
var sayHi = function(): void {
console.log("Привет!");
}
export = sayHi;
importer.ts
import sayHi = require('./exporter');
sayHi();
Теперь нам нужно скачать require.js и подключить его через тег script. Последний шаг состоит в компиляции двух файлов .ts. Необходимо добавить дополнительный параметр, чтобы TypeScript знал, что мы создаем модули для require.js (называемый также AMD), в отличие от CommonJS.
tsc --module amd *.ts
Модули являются довольно сложными для восприятия и выходят за рамки данного руководства. Если вы хотите, чтобы продолжить чтение о них, воспользуйтесь документацией о модулях.
Сторонние объявляющие файлы
При использовании библиотеки, которая была первоначально разработана для обычного JavaScript, нам нужно применить объявляющий файл, чтобы библиотека была совместима с TypeScript. Объявляющий файл имеет расширение .d.ts и содержит различную информацию о библиотеке и ее API.
Объявляющие файлы TypeScript обычно набираются вручную, но высока вероятность того, что нужная вам библиотека имеет файл с расширением .d.ts., созданный кем-то другим. DefinitelyTyped самое большое публичное хранилище файлов для различных библиотек. Существует также популярный Node.js-модуль для управления определениями TypeScript, которые называются Typings.
Если вам все еще нужно написать declaration file самостоятельно, то это руководство поможет вам начать работу.
Заключение
Мы надеемся, что вам понравился этот урок! Изучайте TypeScript и устраивайтесь на более высокооплачиваемую работу!