Тип объединения перечисляет варианты через |: значение типа string | number это либо строка, либо число. Через объединения TypeScript описывает значения, которые законно могут принимать больше одной формы, и компилятор заставляет проверить, какая форма перед вами, прежде чем использовать что-то специфичное для неё.
Внутри каждой ветки проверки typeof у id один тип. Этот шаг называется сужением, и именно он делает объединения практичными.
Разрешены только общие члены
До сужения можно использовать только то, что поддерживает каждый член объединения. toString() есть и у строк, и у чисел, поэтому он допустим; toUpperCase() есть только у строк:
index.ts(3,16): error TS2339: Property 'toUpperCase' does not exist on type 'string | number'.
Property 'toUpperCase' does not exist on type 'number'.
Вторая строка называет член, у которого нет свойства. То же правило действует и в обратную сторону: значение string | number нельзя передать в параметр с типом string (TS2345), потому что оно может оказаться числом. Объединение принимает больше значений, а взамен позволяет делать с ними меньше, пока вы не проверите.
Сужение объединения
Сужение использует обычные проверки JavaScript. TypeScript следит за потоком управления и убирает члены по мере их исключения, так что после последней проверки остаётся один член:
| Проверка | Сужает | Подходит для |
|---|---|---|
typeof x === "string" | до примитива | string, number, boolean, bigint, symbol, undefined, function |
x === null, x === "a" | до сравниваемого значения | null, undefined, литеральные члены |
Array.isArray(x) | до члена-массива | массивы |
x instanceof Date | до класса | экземпляры классов |
"meow" in x | до членов, у которых есть свойство | объектные типы |
x.kind === "circle" | до члена с этим тегом | размеченные объединения |
isCat(x) (возвращает x is Cat) | до того, что говорит функция | что угодно, собственная логика |
Полный список форм сужения есть на странице о сужении типов.
Объединения литеральных типов
Объединение литеральных значений это закрытый набор допустимых значений. Это самое распространённое объединение в реальном коде:
type Status = "idle" | "loading" | "success" | "error";
type Dice = 1 | 2 | 3 | 4 | 5 | 6;
type Toggle = "on" | "off" | boolean; // boolean is itself true | false
let current: Status = "idle";
current = "loading"; // fine
current = "finished"; // error TS2322: Type '"finished"' is not assignable to type 'Status'.
Сравнение с литералом сужает тип: после if (current === "error") ветка else знает, что current это один из трёх оставшихся вариантов. Во многих кодовых базах литеральные объединения заменяют enum; про as const и о том, как вывести такое объединение из массива, см. литеральные типы.
Объединения объектных типов
Если члены это объектные типы, свойства, общие для всех, доступны сразу. Для остальных проверьте наличие свойства через in:
Для объединений нескольких структур объектов более чистый приём это общее литеральное свойство вроде kind: "cat" / kind: "fish". Проверка этого одного свойства сужает весь объект, а switch по нему можно проверить на исчерпывающую обработку. Такой приём называется размеченным объединением.
Массивы и объединения
От того, где стоят скобки, смысл меняется полностью:
| Тип | Означает | Пример значения |
|---|---|---|
(string | number)[] | массив, каждый элемент которого строка или число | [1, "two", 3] |
string[] | number[] | массив только из строк или массив только из чисел | ["a", "b"] |
string | number[] | строка или массив чисел (| связывает слабее, чем []) | "text" |
При переборе (string | number)[] каждый элемент имеет тип объединения, и его нужно сужать, как в колбэке reduce выше. Методы вроде map и filter работают и на string[] | number[], причём колбэк получает string | number.
Объединения с null и undefined
Самое распространённое объединение это «значение или ничего»: string | null, User | undefined. Именно это возвращают Array.prototype.find и Map.prototype.get, а необязательное свойство name?: string читается как string | undefined. Обработке таких значений через ?., ?? и проверки на null посвящена отдельная страница: null и undefined.
Чтобы убрать члены из существующего объединения на уровне типов, используйте встроенные утилиты: Exclude<"a" | "b" | "c", "a"> это "b" | "c", а NonNullable<string | null> это string.
Часто задаваемые вопросы
Что такое тип объединения в TypeScript?
Тип, составленный из нескольких вариантов, соединённых через |. Значение типа string | number может быть строкой или числом. Компилятор позволяет использовать только то, что общее у всех членов, пока вы не сузите значение до одного члена проверкой вроде typeof value === "string".
Почему TypeScript пишет, что свойства нет в типе объединения?
Потому что хотя бы у одного члена объединения его нет. Ошибка TS2339, например Property 'toUpperCase' does not exist on type 'string | number', означает, что значение может оказаться числом, у которого нет toUpperCase. Сначала сузьте тип (typeof, in, Array.isArray, instanceof или проверка дискриминанта), затем используйте свойство конкретного члена.
Как объявить массив, в котором хранится больше одного типа?
Возьмите объединение в скобки: (string | number)[] или Array<string | number>, где каждый элемент может быть любого из типов. string[] | number[] это другое: весь массив состоит либо только из строк, либо только из чисел. Без скобок string | number[] означает строку или массив чисел.
Чем тип объединения отличается от типа пересечения?
Объединение A | B это значение, которое является одним из типов, поэтому можно использовать только их общее. Пересечение A & B это значение, которое является обоими сразу, поэтому у него есть все члены обоих. Для объектных типов A | B принимает больше значений, а A & B требует больше свойств.
Как проверить, какого типа значение объединения?
Используйте проверку времени выполнения, которую понимает TypeScript: typeof x === "string" для примитивов, Array.isArray(x) для массивов, x instanceof Date для классов, "prop" in x для структур объектов или x.kind === "circle", когда у членов есть общий литеральный тег. Для собственной логики напишите функцию защитника типа, которая возвращает x is T.