Перейти к содержимому
Теория · Объединения и литеральные типы · урок 1 из 8

Объединение типов

Чему вы научитесь

Чему вы научитесь:

  • описывать значение, которое может быть одного из нескольких типов;
  • понимать, почему у значения типа string | number доступно только общее для обоих типов;
  • описывать значение, которого может не быть, и работать с ним через ?. и ??;
  • ограничивать значение фиксированным набором вариантов: литеральными типами и enum.
Знак парковки разрешает и string, и number, и рядом годится только инструмент, подходящий обоим

Представьте парковку с табличкой «Только машины или мотоциклы». Вы работаете на ней и подходите к месту, не зная, что на нём стоит. Заправить можно и машину, и мотоцикл: бак есть у обоих. А открыть багажник получится только у машины: если на месте мотоцикл, багажника там нет.

Значения в программах бывают такими же. Номер заказа в магазине может быть числом 101, а может быть строкой "A-7". В JavaScript функция примет и то и другое. В TypeScript у параметра есть тип, и одного number здесь мало:

TypeScript
function printId(id: number) {
    console.log(id);
}

printId(101);
printId("A-7"); // ✗
TEXT
error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

tsc сообщает, что строку нельзя передать в параметр типа number. Нужен способ сказать «число или строка» — такой же, как табличка на парковке.

Тип «одно или другое»

Объединение типов записывают через вертикальную черту: string | number читается как «строка или число». В переменную или параметр такого типа можно положить значение любого из перечисленных типов:

TypeScript
function printId(id: string | number) {
    console.log(id);
}

printId(101);   // 101
printId("A-7"); // A-7

Давайте разберём пример по строкам:

  1. id: string | number разрешает параметру принимать и строку, и число.
  2. printId(101) передаёт число и печатает 101.
  3. printId("A-7") передаёт строку и печатает A-7.

Объединение пишут везде, где нужен тип:

  • у переменной: в let id: string | number = 101; позже можно положить "A-7", а true уже нельзя;
  • у параметра функции, как в примере выше;
  • у результата функции, после скобок с параметрами.

Типов в объединении может быть больше двух: string | number | boolean.

Что можно делать со значением

Тут снова пригодится табличка на парковке. Внутри printId TypeScript не знает, что пришло на этот раз: число или строка. Поэтому он разрешает только то, что умеют оба типа:

TypeScript
function printId(id: string | number) {
    console.log(id.toString());
    console.log(id.toUpperCase()); // ✗
}
TEXT
error TS2339: Property 'toUpperCase' does not exist on type 'string | number'.
  Property 'toUpperCase' does not exist on type 'number'.

Разберём, почему одна строка прошла, а другая нет:

  1. id.toString() компилируется: метод toString есть и у строки, и у числа. Это как заправка, которая подходит любому транспорту на парковке.
  2. id.toUpperCase() вызывает ошибку, и вторая строка сообщения называет причину: у number такого метода нет. Это багажник, которого у мотоцикла не бывает.

Если бы JavaScript выполнил эту функцию с числом, программа упала бы с TypeError: id.toUpperCase is not a function. TypeScript находит эту ошибку заранее, ещё до запуска.

Пройдите пример по шагам и следите, что подсказка редактора говорит про выделенное имя на каждой строке:

ВживуюЧто видит TypeScriptшаг 1 из 5

Параметр id может оказаться и строкой, и числом.

Нажимайте «Следующий шаг»: подсказка покажет тип или ошибку на выделенной строке

Здесь аналогия расходится с TypeScript в одном. На парковке можно подойти и посмотреть, что стоит на месте, а компилятор видит только табличку — тип, а не само значение. Чтобы вызвать метод одного из типов, значение сначала проверяют, например через typeof. Внутри if (typeof id === "string") TypeScript знает, что id — строка.

Этот вопрос часто задают на собеседовании: «Что можно делать со значением типа A | B?» Ответ: только то, что есть у каждого типа из объединения.

Частая ошибка

Значение типа-объединения передали туда, где ждут один-единственный тип:

TypeScript
function shout(text: string) {
    console.log(text.toUpperCase());
}

function printId(id: string | number) {
    shout(id); // ✗
}
TEXT
error TS2345: Argument of type 'string | number' is not assignable to parameter of type 'string'.
  Type 'number' is not assignable to type 'string'.

Функция shout принимает только строку, а в id может оказаться число — об этом говорит вторая строка сообщения. Исправить можно общим методом: shout(id.toString()) передаёт строку при любом значении id.

Главное

  • Объединение string | number значит «строка или число»: в переменную или параметр такого типа можно передать значение любого из этих типов.
  • Со значением типа-объединения можно делать только то, что есть у каждого его типа: toString() можно, toUpperCase() — нельзя.
  • Значение типа string | number нельзя передать туда, где ждут только string.
  • Компилятор видит тип, а не значение, поэтому такие ошибки он находит ещё до запуска.