Объединение A | B значит «значение одного из типов». Пока TypeScript не знает, какого именно, доступны только общие для всех членов операции. Сужение (narrowing) это анализ кода, после которого компилятор в конкретной ветке знает тип точнее.
Способы сужения
function show(x: string | number | null) {
if (x === null) return; // проверка равенства
if (typeof x === 'string') { // typeof
x.toUpperCase(); // x: string
} else {
x.toFixed(2); // x: number
}
}
typeofразличает'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function'. Помните:typeof null === 'object'.- Проверка на истинность (
if (x)) убираетnullиundefined, но заодно отсекает'',0,NaN,false. Частый баг с числами. instanceofсужает до класса:if (e instanceof Error) e.message.inпроверяет наличие свойства:if ('swim' in animal).Array.isArray(x)сужает до массива.- Присваивание тоже сужает: после
let v: string | number = 'a'переменная до следующего присваивания считаетсяstring.
Дискриминированные объединения
Самый надёжный приём: у каждого варианта есть общее поле с литеральным типом (тег).
type Result =
| { status: 'ok'; data: string }
| { status: 'error'; error: Error };
function handle(r: Result) {
switch (r.status) {
case 'ok': return r.data; // r: { status: 'ok'; data: string }
case 'error': throw r.error;
}
}
Так моделируют состояния загрузки, экшены редьюсера, события.
Исчерпывающая проверка через never
Если в default присвоить значение переменной типа never, компилятор упадёт, когда кто-то добавит новый вариант и забудет его обработать:
default: {
const _never: never = r;
throw new Error('unreachable');
}
Пользовательские type guards
Функция с возвращаемым типом x is T сообщает компилятору результат проверки:
function isUser(v: unknown): v is User {
return typeof v === 'object' && v !== null && 'id' in v;
}
Компилятор верит guard'у на слово: если проверка внутри неверна, типы будут врать. С TypeScript 5.5 простые стрелки вроде x => x !== null выводятся как предикаты автоматически, поэтому arr.filter(x => x !== null) уже даёт массив без null.
Функция-утверждение asserts v is T сужает тип после вызова, а при неудаче должна бросить исключение.
Где сужение теряется
- В колбэке: сужение переменной
letпереносится внутрь замыкания, только если ниже ей больше ничего не присваивают (так стало с TypeScript 5.4). Иначе к моменту вызова значение могло измениться, и сужение теряется. Сconstи параметрами без переприсваивания работает всегда. - После вызова функции сужение свойств объекта (
obj.x) сохраняется, хотя функция могла их поменять. Это осознанный компромисс. asне сужает, а заставляет: проверки нет.
Частые ошибки
if (count)дляnumber | undefined: ноль уходит в ветку «нет значения».- Тег не литеральный (
status: string): дискриминации не будет. as Userвместо проверки данных извне.
Что спросят на собесе
- Какие способы сужения знаете и чем
inотличается отinstanceof. - Что такое дискриминированное объединение и зачем тег.
- Как сделать исчерпывающую проверку
switch. - Что такое type predicate
x is Tи чем опасен. - Почему
asне равно проверке.