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

Литеральные типы

На сайте магазина размер одежды выбирают из выпадающего списка: S, M или L. Вписать в него «XL» или «m» нельзя — список предлагает только свои варианты. Будь вместо списка обычное текстовое поле, покупатель ввёл бы что угодно, и магазину пришлось бы гадать, какой размер означает «эм».

Параметр типа string — то самое текстовое поле. Функция order(size: string) примет и "M", и опечатку "m", и tsc промолчит. Нужен тип, который работает как выпадающий список.

Вы уже видели литеральный тип: у const b = 5 тип 5, и в нём ровно одно значение. Если объединить несколько таких типов, получится список вариантов.

Список из литералов

Объединение литеральных типов перечисляет все допустимые значения. Чтобы не повторять его в каждом месте, ему дают имя — ключевым словом type:

TypeScript
type Size = "S" | "M" | "L";

function order(size: Size) {
    console.log("size " + size);
}

order("M"); // size M
order("L"); // size L

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

  1. type Size = "S" | "M" | "L"; объявляет имя: Size теперь означает «"S", "M" или "L"».
  2. order(size: Size) принимает только эти три строки.
  3. order("M") и order("L") передают значения из списка и печатают size M и size L.

Имя Size не создаёт ничего нового при запуске. Это только имя для типа, и tsc стирает его вместе с остальными типами. Зато редактор знает список вариантов: внутри кавычек в order("") он подскажет "S", "M" и "L".

Значение не из списка

Имя Size подходит и для переменной. Попробуем положить в неё размер, которого в списке нет:

TypeScript
type Size = "S" | "M" | "L";

let mySize: Size = "M";
mySize = "XL"; // ✗
TEXT
error TS2322: Type '"XL"' is not assignable to type 'Size'.

В сообщении '"XL"' — литеральный тип значения "XL". tsc говорит, что этот тип не входит в Size. Опечатку вроде "m" tsc тоже отклонит и даже спросит Did you mean '"M"'?: TypeScript различает большие и маленькие буквы.

Здесь аналогия со списком заканчивается. Список на сайте проверяет выбор, пока страница работает, а литеральный тип проверяется только при компиляции. Строку, которая придёт из формы или с сервера во время работы программы, tsc проверить не сможет: при запуске типов уже нет.

Этот вопрос часто задают на собеседовании: «Чем "a" | "b" отличается от string?» Ответ: string допускает любую строку, а "a" | "b" — ровно две, и опечатку tsc найдёт ещё до запуска.

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

Размер сначала положили в переменную через let, а потом передали в функцию:

TypeScript
type Size = "S" | "M" | "L";

function order(size: Size) {
    console.log("size " + size);
}

let choice = "M";
order(choice); // ✗
TEXT
error TS2345: Argument of type 'string' is not assignable to parameter of type 'Size'.

В choice лежит "M", а tsc всё равно сообщает об ошибке. Причина — расширение типа: переменной let позже можно присвоить другую строку, поэтому TypeScript выводит для неё тип string, а не "M". Весь string к параметру Size не подходит. Литерал, написанный прямо в вызове, как order("M"), свой тип сохраняет, поэтому там ошибки нет.

Исправить можно двумя способами:

TypeScript
type Size = "S" | "M" | "L";

function order(size: Size) {
    console.log("size " + size);
}

const first = "M";
let second: Size = "L";
order(first);  // size M
order(second); // size L

Объявить через const

const first = "M" уже не изменится, поэтому TypeScript оставляет ей литеральный тип "M". Этот тип входит в Size, и вызов компилируется.

Указать тип явно

let second: Size = "L" остаётся переменной, но аннотация разрешает класть в неё только значения из Size. Так удобно, когда размер позже меняется.

Главное

  • Литеральный тип "M" допускает ровно одно значение, а объединение "S" | "M" | "L" — список вариантов.
  • type Size = … даёт объединению имя; при компиляции оно стирается.
  • Значение не из списка tsc отклоняет: Type '"XL"' is not assignable to type 'Size'.
  • let choice = "M" получает тип string и к Size не подходит; помогают const или аннотация : Size.