Мы рассмотрели основные возможности системы типов TypeScript, когда обсуждали Почему TypeScript?. Ниже приводятся некоторые ключевые выводы из этого обсуждения, которые не нуждаются в дальнейшем объяснении:
- Система типов в TypeScript спроектирована быть необязательной, так чтобы ваш JavaScript был TypeScript.
- TypeScript не блокирует генерацию JavaScript при наличии ошибок типов, что позволяет вам постепенно обновлять JS до TS.
Теперь давайте начнем с синтаксиса системы типов TypeScript. Таким образом, вы можете сразу начать использовать эти описания в своем коде и увидеть преимущества. Это подготовит вас к углублению позже.
Как упоминалось ранее, типы описываются с использованием синтаксиса :TypeAnnotation. Все, что доступно в области объявления типа, может использоваться как описание типа.
В следующем примере демонстрируются описания типов для переменных, параметров функции и возвращаемых значений функции:
var num: number = 123;
function identity(num: number): number {
return num;
}Простые типы JavaScript хорошо представлены в системе типов TypeScript. Это означает обработку string, number, boolean как показано ниже:
var num: number;
var str: string;
var bool: boolean;
num = 123;
num = 123.456;
num = '123'; // Ошибка
str = '123';
str = 123; // Ошибка
bool = true;
bool = false;
bool = 'false'; // ОшибкаTypeScript предоставляет особый синтаксис типов для массивов, чтобы вам было легче описывать и документировать свой код. Синтаксис в основном заключается в добавлении постфикса [] к любому валидному описанию типа (например, :boolean[]). Это позволяет вам безопасно выполнять любые манипуляции с массивами, которые вы обычно делаете, и защищает вас от ошибок, таких как присвоение неправильного типа. Это продемонстрировано ниже:
var boolArray: boolean[];
boolArray = [true, false];
console.log(boolArray[0]); // true
console.log(boolArray.length); // 2
boolArray[1] = true;
boolArray = [false, false];
boolArray[0] = 'false'; // Ошибка!
boolArray = 'false'; // Ошибка!
boolArray = [true, 'false']; // Ошибка!Интерфейсы - это основной способ в TypeScript объединять описания нескольких типов в одно именованное описание. Рассмотрим следующий пример:
interface Name {
first: string;
second: string;
}
var name: Name;
name = {
first: 'John',
second: 'Doe'
};
name = { // Ошибка : `second` отсутствует
first: 'John'
};
name = { // Ошибка : `second` имеет неправильный тип
first: 'John',
second: 1337
};Здесь мы соединили описание first: string + second: string в новое описание Name, которое обеспечивает проверку типов отдельных элементов. Интерфейсы имеют большой потенциал в TypeScript, и мы посвятим целый раздел тому, как вы можете использовать это в своих интересах.
Вместо создания нового interface вы можете описать все, что вы хотите встроенно, используя :{ /*Structure*/ }. Предыдущий пример представлен снова уже как встроенный тип:
var name: {
first: string;
second: string;
};
name = {
first: 'John',
second: 'Doe'
};
name = { // Ошибка : `second` отсутствует
first: 'John'
};
name = { // Ошибка : `second` имеет неправильный тип
first: 'John',
second: 1337
};Встроенные типы отлично подходят для быстрого однократного предоставления описания для чего-либо. Это избавляет вас от необходимости придумывать (потенциально плохое) имя типа. Однако, если вы обнаружите, что вставляете одно и то же встраиваемое описание типа несколько раз, неплохо было бы подумать о рефакторинге его в интерфейс (или псевдоним типа, описанный далее в этом разделе).
Помимо рассмотренных простых типов, есть несколько типов, которые имеют особое значение в TypeScript. Это any, null, undefined, void.
Тип any занимает особое место в системе типов TypeScript. Он дает вам запасной выход из системы типов, чтобы заставить компилятор отстать от вас. any совместим с любыми типами в системе типов. Это означает, что что угодно может быть присвоено ему и он может быть присвоен чему угодно. Это продемонстрировано в примере ниже:
var power: any;
// Принимает все типы
power = '123';
power = 123;
// Совместим со всеми типами
var num: number;
power = num;
num = power;Если вы переводите код JavaScript на TypeScript, вы вначале станете близкими друзьями с any. Однако не воспринимайте эту дружбу всерьёз, поскольку его использование означает, что вы сами должны обеспечить безопасность типа. Вы в основном говорите компилятору не делать никакого значимого статического анализа.
То, как они обрабатываются системой типов, зависит от флага компилятора strictNullChecks (мы рассмотрим этот флаг позже). Когда strictNullCheck:false, литералы JavaScript null и undefined эффективно обрабатываются системой типов как что-то наподобие any. Эти литералы могут быть назначены любому другому типу. Это продемонстрировано в следующем примере:
var num: number;
var str: string;
// Эти литералы могут быть назначены на что угодно
num = null;
str = undefined;Используйте :void, чтобы описать, что функция ничего не возвращает:
function log(message): void {
console.log(message);
}Многие алгоритмы и структуры данных в информатике не полагаются на текущий тип объекта. Тем не менее, вы все еще хотите наложить ограничения между различными переменными. Простым мини-примером является функция, которая принимает список элементов и возвращает перевернутый список элементов. Здесь есть ограничение между тем, что передается функции, и тем, что возвращается функцией:
function reverse<T>(items: T[]): T[] {
var toreturn = [];
for (let i = items.length - 1; i >= 0; i--) {
toreturn.push(items[i]);
}
return toreturn;
}
var sample = [1, 2, 3];
var reversed = reverse(sample);
console.log(reversed); // 3,2,1
// Защита!
reversed[0] = '1'; // Ошибка!
reversed = ['1', '2']; // Ошибка!
reversed[0] = 1; // Хорошо
reversed = [1, 2]; // ХорошоЗдесь вы по сути говорите, что функция reverse принимает массив (items: T[]) элементов какого-то типа T (обратите внимание на параметр типа в reverse<T>) и возвращает массив элементов типа T (обратите внимание : T[]). Поскольку функция reverse возвращает элементы того же типа, что и приняла, TypeScript знает, что переменная reversed также имеет тип number[] и обеспечит вам защиту типа. Точно так же, если вы передаете массив string[] в функцию reverse, возвращаемый результат также является массивом string[], и вы получаете защиту типов, аналогичную показанной ниже:
var strArr = ['1', '2'];
var reversedStrs = reverse(strArr);
reversedStrs = [1, 2]; // Ошибка!На самом деле JavaScript массивы уже имеют функцию .reverse, а TypeScript фактически использует дженерики для определения её структуры:
interface Array<T> {
reverse(): T[];
// ...
}Это означает, что вы получаете защиту типов при вызове .reverse для любого массива, как показано ниже:
var numArr = [1, 2];
var reversedNums = numArr.reverse();
reversedNums = ['1', '2']; // Ошибка!Мы обсудим больше интерфейс Array<T> позже, когда расскажем про lib.d.ts в разделе Объявления окружения.
Как правило, в JavaScript вы хотите, чтобы свойство было одним из нескольких типов, например string или number. Здесь пригодится тип объединения (обозначаемый через | в описании типа, например, string|number). Обычный вариант использования - это функция, которая может принимать одиночный элемент или массив элементов, например:
function formatCommandline(command: string[]|string) {
var line = '';
if (typeof command === 'string') {
line = command.trim();
} else {
line = command.join(' ').trim();
}
// Делаем всякие штуки со строкой: строка
}extend - это очень распространенный паттерн в JavaScript, где вы берете два элемента и создаете новый, который имеет функции обоих этих объектов. Тип пересечения позволяет вам безопасно использовать этот паттерн, как показано ниже:
function extend<T, U>(first: T, second: U): T & U {
return { ...first, ...second };
}
const x = extend({ a: "hello" }, { b: 42 });
// x теперь имеет и `a`, и` b`
const a = x.a;
const b = x.b;В JavaScript нет поддержки кортежей. Разработчики обычно используют массив в качестве кортежа. Но зато система типов TypeScript поддерживает кортежи. Кортежи могут быть описаны с помощью : [typeofmember1, typeofmember2] и т.д. Кортеж может иметь любое количество элементов. Кортежи демонстрируются в следующем примере:
var nameNumber: [string, number];
// Хорошо
nameNumber = ['Jenny', 8675309];
// Ошибка!
nameNumber = ['Jenny', '867-5309'];В сочетании с поддержкой деструктуризации в TypeScript, кортежи чувствуют себя довольно прекрасно, несмотря на то, что под ними расположены простые массивы:
var nameNumber: [string, number];
nameNumber = ['Jenny', 8675309];
var [name, num] = nameNumber;TypeScript предоставляет удобный синтаксис для указания имен описания типов, которые вы хотели бы использовать более чем в одном месте. Псевдонимы создаются с использованием синтаксиса type SomeName = someValidTypeAnnotation. Пример демонстрируется ниже:
type StrOrNum = string|number;
// Использование: как и любая другая запись
var sample: StrOrNum;
sample = 123;
sample = '123';
// Просто проверяю
sample = true; // Ошибка!В отличие от interface, вы можете дать псевдоним типа буквально для описания любого типа (полезно для таких вещей, как типы объединения и пересечения). Вот еще несколько примеров, чтобы вы познакомились с синтаксисом:
type Text = string | { text: string };
type Coordinates = [number, number];
type Callback = (data: string) => void;СОВЕТ: Если вам нужны иерархии описаний типа, используйте
interface. Их можно использовать сimplementsиextends
СОВЕТ: используйте псевдоним типа для более простых структур объектов (например,
Coordinates), просто чтобы дать им семантическое имя. Также, когда вы хотите дать семантические имена для типов объединения или пересечения, псевдонимы типов - это верный способ.
Теперь, когда вы можете начать описывать большую часть своего кода JavaScript, мы можем перейти к мельчайшим подробностям всей мощи, доступной в системе типов TypeScript.