Введение в TypeScript
Что такое TypeScript?
TypeScript — это язык программирования со строгой типизацией, основанный на JavaScript. Первоначально он был разработан Андерсом Хейлсбергом в 2012 году, а сейчас развивается и поддерживается Microsoft как проект с открытым исходным кодом.
TypeScript компилируется в JavaScript и может выполняться в любой среде выполнения JavaScript (например, в браузере или в Node.js на сервере).
Он поддерживает несколько парадигм программирования, в том числе функциональное, обобщённое, императивное и объектно-ориентированное программирование, и является компилируемым (транспилируемым) языком, который перед выполнением преобразуется в JavaScript.
Зачем нужен TypeScript?
TypeScript — это язык со строгой типизацией, который помогает предотвращать распространённые ошибки программирования и избегать некоторых видов ошибок времени выполнения ещё до запуска программы.
Язык со строгой типизацией позволяет разработчику задавать различные ограничения и варианты поведения программы в определениях типов данных, упрощая проверку корректности программного обеспечения и предотвращение дефектов. Это особенно ценно для крупных приложений.
Некоторые преимущества TypeScript:
- Статическая типизация, опционально — строгая
- Вывод типов
- Доступ к возможностям ES6 и ES7
- Кроссплатформенная и кроссбраузерная совместимость
- Поддержка инструментов с IntelliSense
TypeScript и JavaScript
Код TypeScript записывается в файлах .ts или .tsx, а код JavaScript — в файлах .js или .jsx.
Файлы с расширением .tsx или .jsx могут содержать расширение синтаксиса JavaScript — JSX, которое используется в React для разработки пользовательских интерфейсов.
С точки зрения синтаксиса TypeScript является типизированным надмножеством JavaScript (ECMAScript 2015). Любой код JavaScript является допустимым кодом TypeScript, но обратное верно не всегда.
Например, рассмотрим функцию в JavaScript-файле с расширением .js:
const sum = (a, b) => a + b;Эту функцию можно преобразовать и использовать в TypeScript, изменив расширение файла на .ts. Однако если ту же функцию снабдить аннотациями типов TypeScript, её нельзя будет выполнить ни в одной среде выполнения JavaScript без компиляции. Следующий код TypeScript приведёт к синтаксической ошибке, если его не скомпилировать:
const sum = (a: number, b: number): number => a + b;TypeScript был создан для выявления потенциальных ошибок времени выполнения на этапе компиляции: разработчики могут выражать свои намерения с помощью аннотаций типов. Кроме того, благодаря выводу типов TypeScript способен обнаруживать некоторые проблемы даже без явных аннотаций типов. Например, в следующем фрагменте кода не указаны никакие типы TypeScript:
const items = [{ x: 1 }, { x: 2 }];const result = items.filter(item => item.y);В этом случае TypeScript обнаруживает ошибку и сообщает:
Property 'y' does not exist on type '{ x: number; }'.Система типов TypeScript во многом учитывает поведение JavaScript во время выполнения. Например, оператор сложения (+), который в JavaScript может выполнять как конкатенацию строк, так и сложение чисел, моделируется в TypeScript таким же образом:
const result = '1' + 1; // Result is of type stringКоманда разработчиков TypeScript приняла осознанное решение отмечать необычное использование JavaScript как ошибку. Например, рассмотрим следующий допустимый код JavaScript:
const result = 1 + true; // In JavaScript, the result is equal to 2Однако TypeScript выдаёт ошибку:
Operator '+' cannot be applied to types 'number' and 'boolean'.Эта ошибка возникает потому, что TypeScript строго контролирует совместимость типов и в данном случае обнаруживает недопустимую операцию между числом и логическим значением.
Генерация кода с помощью TypeScript
У компилятора TypeScript две основные задачи: проверка типов и компиляция в JavaScript. Эти два процесса не зависят друг от друга. Типы не влияют на выполнение кода в среде выполнения JavaScript, поскольку при компиляции полностью удаляются. TypeScript может сгенерировать JavaScript-код, даже если присутствуют ошибки типов. Ниже приведён пример кода TypeScript с ошибкой типа:
const add = (a: number, b: number): number => a + b;const result = add('x', 'y'); // Argument of type 'string' is not assignable to parameter of type 'number'.Тем не менее он может сгенерировать исполняемый код JavaScript:
'use strict';const add = (a, b) => a + b;const result = add('x', 'y'); // xyПроверить типы TypeScript во время выполнения невозможно. Например:
interface Animal { name: string;}interface Dog extends Animal { bark: () => void;}interface Cat extends Animal { meow: () => void;}const makeNoise = (animal: Animal) => { if (animal instanceof Dog) { // 'Dog' only refers to a type, but is being used as a value here. // ... }};Поскольку после компиляции типы удаляются, выполнить этот код в JavaScript невозможно. Чтобы распознавать типы во время выполнения, необходимо использовать другой механизм. TypeScript предоставляет несколько вариантов, один из распространённых — «дискриминируемое объединение». Например:
interface Dog { kind: 'dog'; // Tagged union bark: () => void;}interface Cat { kind: 'cat'; // Tagged union meow: () => void;}type Animal = Dog | Cat;
const makeNoise = (animal: Animal) => { if (animal.kind === 'dog') { animal.bark(); } else { animal.meow(); }};
const dog: Dog = { kind: 'dog', bark: () => console.log('bark'),};makeNoise(dog);Свойство «kind» представляет собой значение, которое можно использовать во время выполнения, чтобы различать объекты в JavaScript.
Также значение во время выполнения может иметь тип, отличный от указанного в объявлении типа. Например, это может произойти, если разработчик неправильно понял тип API и указал для него неверную аннотацию.
TypeScript является надмножеством JavaScript, поэтому ключевое слово «class» можно использовать как тип и как значение во время выполнения.
class Animal { constructor(public name: string) {}}class Dog extends Animal { constructor( public name: string, public bark: () => void ) { super(name); }}class Cat extends Animal { constructor( public name: string, public meow: () => void ) { super(name); }}type Mammal = Dog | Cat;
const makeNoise = (mammal: Mammal) => { if (mammal instanceof Dog) { mammal.bark(); } else { mammal.meow(); }};
const dog = new Dog('Fido', () => console.log('bark'));makeNoise(dog);В JavaScript у «class» есть свойство «prototype», а оператор «instanceof» позволяет проверить, встречается ли свойство prototype конструктора где-либо в цепочке прототипов объекта.
TypeScript не влияет на производительность во время выполнения, поскольку все типы удаляются. Однако TypeScript создаёт некоторые дополнительные затраты времени на сборку.
Современный JavaScript уже сейчас (понижение версии)
TypeScript может компилировать код в любую выпущенную версию JavaScript, начиная с ECMAScript 3 (1999). Это означает, что TypeScript может транспилировать код, использующий новейшие возможности JavaScript, в более старые версии. Этот процесс называется понижением целевой версии (Downleveling). Благодаря этому можно использовать современный JavaScript, сохраняя максимальную совместимость со старыми средами выполнения.
Важно отметить, что при транспиляции в более старую версию JavaScript TypeScript может сгенерировать код, который приведёт к снижению производительности по сравнению с нативными реализациями.
Ниже перечислены некоторые современные возможности JavaScript, которые можно использовать в TypeScript:
- Модули ECMAScript вместо обратных вызовов «define» в стиле AMD или инструкций «require» CommonJS.
- Классы вместо прототипов.
- Объявление переменных с помощью «let» или «const» вместо «var».
- Цикл «for-of» или метод «.forEach» вместо традиционного цикла «for».
- Стрелочные функции вместо функциональных выражений.
- Деструктурирующее присваивание.
- Сокращённые имена свойств и методов, а также вычисляемые имена свойств.
- Параметры функций по умолчанию.
Используя эти современные возможности JavaScript, разработчики могут писать на TypeScript более выразительный и лаконичный код.