Литеральные типы
На сайте магазина размер одежды выбирают из выпадающего списка: S, M или L. Вписать в него «XL» или «m» нельзя — список предлагает только свои варианты. Будь вместо списка обычное текстовое поле, покупатель ввёл бы что угодно, и магазину пришлось бы гадать, какой размер означает «эм».
Параметр типа string — то самое текстовое поле. Функция order(size: string) примет и "M", и опечатку "m", и tsc промолчит. Нужен тип, который работает как выпадающий список.
Вы уже видели литеральный тип: у const b = 5 тип 5, и в нём ровно одно значение. Если объединить несколько таких типов, получится список вариантов.
Список из литералов
Объединение литеральных типов перечисляет все допустимые значения. Чтобы не повторять его в каждом месте, ему дают имя — ключевым словом type:
type Size = "S" | "M" | "L";
function order(size: Size) {
console.log("size " + size);
}
order("M"); // size M
order("L"); // size LДавайте разберём пример по строкам:
type Size = "S" | "M" | "L";объявляет имя:Sizeтеперь означает «"S","M"или"L"».order(size: Size)принимает только эти три строки.order("M")иorder("L")передают значения из списка и печатаютsize Mиsize L.
Имя Size не создаёт ничего нового при запуске. Это только имя для типа, и tsc стирает его вместе с остальными типами. Зато редактор знает список вариантов: внутри кавычек в order("") он подскажет "S", "M" и "L".
Значение не из списка
Имя Size подходит и для переменной. Попробуем положить в неё размер, которого в списке нет:
type Size = "S" | "M" | "L";
let mySize: Size = "M";
mySize = "XL"; // ✗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, а потом передали в функцию:
type Size = "S" | "M" | "L";
function order(size: Size) {
console.log("size " + size);
}
let choice = "M";
order(choice); // ✗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"), свой тип сохраняет, поэтому там ошибки нет.
Исправить можно двумя способами:
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.