Zero to Hero [ru]
В наши дни, чтобы быть конкурентоспособным в мире фронтенда и Node.js, необходимо знать TypeScript.
Хотя JavaScript, который мы знаем и любим, великолепен, как только код становится немного сложнее, появляются проблемы с его поддержкой. Рассмотрим следующий пример:
const userColorMap = new Map({
'123': ['red', 'blue', 'green'],
'456': ['yellow', 'purple', 'orange'],
'789': ['pink', 'black', 'white'],
});
const userFavouriteColors = userId => {
return userColorMap.get(userId);
};
Выглядит довольно просто, не так ли? Допустим, мы используем эту функцию так:
const favouriteColors = userFavouriteColors('123');
// Добавляем новый цвет к любимым цветам пользователя
favouriteColors.push('brown');
На первый взгляд всё хорошо, но что, если мы сделаем так:
const favouriteColors = userFavouriteColors('000');
// Добавляем новый цвет к любимым цветам пользователя
favouriteColors.push('brown');
Что произойдет? Мы получим знакомую ошибку:
TypeError: Cannot read properties of null (reading 'push')
В чём здесь проблема? Мы передали строку, которой нет в нашем объекте ('000'
), и в результате получили null
. Попытка вызвать метод push
на этом значении вызвала ошибку.
Однако мы, как умные разработчики, могли бы сделать следующее:
const favouriteColors = userFavouriteColors('000') || [];
// Добавляем новый цвет к любимым цветам пользователя
favouriteColors.push('brown');
В таком случае ошибка не возникнет. Но для этого нужно понимать внутреннюю логику функции, чтобы правильно обработать её результат.
Если бы существовал способ заранее знать, что userFavouriteColors
может вернуть null
, и обработать этот случай! Именно здесь на помощь приходит TypeScript.
Что такое TypeScript?
TypeScript — это язык программирования, который является надмножеством JavaScript. Это значит, что любой корректный код JavaScript также является корректным кодом TypeScript. Позже мы углубимся в детали того, как он работает, а пока давайте рассмотрим примеры.
Вот простой код на JavaScript:
let x = 10;
x = 'hello';
console.log(x);
Он выполнится без ошибок, и на выходе мы получим hello
. С помощью TypeScript мы можем добавить аннотации типов переменным, чтобы гарантировать, что они будут определённого типа. Допустим, в этом примере мы хотим, чтобы x
всегда был числом. Вот как это сделать:
let x: number = 10;
x = 'hello';
console.log(x);
Если вы впервые видите TypeScript, то : number
может быть вам незнакомым! Это аннотация типа, которая говорит TypeScript, что x
всегда должно быть числом. Если мы попытаемся присвоить строку переменной x
, мы получим ошибку:
Type 'string' is not assignable to type 'number'.
Здорово! Теперь мы знаем, что x
всегда будет числом, и если попытаемся присвоить ему строку, TypeScript предупредит нас.
Базовые типы
В TypeScript есть несколько базовых типов:
number
: для любых чисел, включая целые и числа с плавающей запятойstring
: для строковых значенийboolean
: для логических значений (true
илиfalse
)symbol
: для уникальных идентификаторов, созданных черезSymbol()
(часто используется в качестве ключей объектов — если вы не использовали символы ранее, не переживайте)null
: для значенияnull
undefined
: для значенияundefined
Если вы работаете с другими языками, отличными от JavaScript, вы могли привыкнуть к разделению целых чисел и чисел с плавающей запятой, а также строк и символов. Поскольку TypeScript является надмножеством JavaScript, а JavaScript не делает таких различий, TypeScript также их не делает. В JavaScript как 10
, так и 10.0
являются числами, и нет понятия строкового символа, как в других языках.
Песочница TypeScript
Теперь, когда мы начали разбирать код, давайте попробуем выполнить его! Настроить TypeScript в вашей среде разработки довольно просто, но могут возникнуть небольшие трудности. Существует множество статей по настройке TypeScript локально, но для этой статьи откройте песочницу TypeScript здесь. Это позволит вам писать код TypeScript прямо в браузере без необходимости настройки среды разработки!
Попробуйте выполнить следующий код в песочнице:
let n: number = 10;
let s: string = 'hello';
let b: boolean = true;
let nullValue: null = null;
let undefinedValue: undefined = undefined;
console.log(n.length);
console.log(s.length);
console.log(b.length);
Вы сразу заметите, что первая и третья строки подчеркнуты красным, что указывает на наличие проблемы. Это круто, не так ли? Ошибки будут выглядеть примерно так: Property 'length' does not exist on type 'number'.
. Это ошибка компилятора TypeScript, сообщающая нам, что что-то пойдёт не так ещё до выполнения кода. Здорово!
Краткое техническое примечание
Мы только что упомянули "компилятор TypeScript". Это потому, что TypeScript является компилируемым языком, то есть код, который фактически выполняется — это JavaScript, преобразованный из вашего TypeScript-кода. Когда TypeScript компилирует ваш код, он проверяет наличие ошибок и уведомляет вас, если обнаруживает проблемы, как в приведённом выше примере. Аннотации типов, которые вы добавляете в ваш TypeScript-код, на самом деле не меняют поведение вашего кода во время выполнения — они существуют только для помощи TypeScript в определении типов ваших переменных.
Два дополнительных типа: any
и unknown
Существуют два дополнительных типа, о которых стоит знать: any
и unknown
.
any
— это тип, который отключает проверку типов для переменной. Это означает, что если вы присвоите переменной x
тип any
, TypeScript не будет проверять аннотации типов на x
, и вы сможете присвоить ей любое значение. Вы сможете увидеть это в коде проектов, которые постепенно переходят на TypeScript. Начинать с any
и постепенно удалять аннотации типов гораздо сложнее, поэтому я настоятельно рекомендую начинать с реальных типов, насколько это возможно.
unknown
— это тип, который представляет любое значение. Вы можете рассматривать его как более безопасную версию any
. Если вы попытаетесь что-либо сделать со значением unknown
, вам нужно будет выполнить некоторые проверки во время выполнения, чтобы убедиться, что это безопасно. См. раздел «Сужение типов» ниже для получения дополнительной информации об этом.
Никогда не говори никогда: never
never
— это тип, который используется д ля обозначения значений, которые никогда не должны существовать. Он имеет несколько ключевых применений, связанных с невозможностью или недостижимостью определённых состояний:
- Функции, которые не возвращают значения (например, бросают ошибку или бесконечный цикл):
function throwError(): never {
throw new Error('Error');
}
- Недостижимый код:
type Shape = 'circle' | 'square';
function getArea(shape: Shape): number {
if (shape === 'circle') return 1;
if (shape === 'square') return 2;
const _: never = shape; // Ошибка, если в Shape появится новый тип
}
- В недопустимых типах:
type ProhibitedNumber = Extract<string | number, never>;
// Результат — `never`, так как ничего не подходит.
Особенности:
never
— подтип всех типов.- Невозможно создать значение типа
never
Массивы
Теперь, когда мы знаем основные типы (number, string, boolean), давайте рассмотрим, как использовать массивы в TypeScript:
let numbers: number[] = [1, 2, 3];
let strings: string[] = ['a', 'b', 'c'];
let booleans: boolean[] = [true, false, true];
// Это действие приведёт к ошибке:
booleans.push('hello');
Пока это должно выглядеть знакомо: вы привыкли использовать квадратные скобки для создания массивов в JavaScript, а теперь в TypeScript вы создаёте тип массива, используя имя типа, за которым следуют квадратные скобки ([]
).