Сужение типа (Type Narrowing) в TypeScript — это процесс, при котором компилятор "понимает", что в определённом участке кода переменная однозначно имеет более конкретный тип, исходя из условий.
Типичные приёмы сужения:
typeof:function example(x: number | string) { if (typeof x === 'string') { // x: string здесь return x.toUpperCase(); } else { // x: number здесь return x.toFixed(2); } }
instanceof (для классов):if (dateObj instanceof Date) { // dateObj: Date }
null и undefined:function print(value?: string) { if (value != null) { // value: string console.log(value.length); } }
type Pet = { kind: 'dog'; woof: () => void } | { kind: 'cat'; meow: () => void }; function sound(pet: Pet) { if (pet.kind === 'dog') { pet.woof(); } else { pet.meow(); } }
TypeScript поддерживает и пользовательские функции-предикаты:
function isString(x: unknown): x is string { return typeof x === 'string'; }
Сужение делает проверки типов безопаснее и код — более надёжным.
Можно ли гарантировать сужение типа через обычные сравнения (например,
==/===), и всегда ли оно работает?
Ответ: Нет. Не во всех случаях TypeScript понимает тип из простых сравнений, особенно если сравнение слишком "туманное" или происходит через косвенные переменные/свойства. Для сужения часто нужно использовать явные механики (typeof, instanceof, дискриминантные свойства и type guard'ы).
Пример:
function foo(x: number | string | null) { if (x) { // x: string | number, null уже невозможен, но сужения до конкретного не будет } }
История
В крупном TypeScript-проекте условие типа user.role == 'admin' не сузило тип данных, и всё равно нужно было прописывать проверки на существование свойства. Разработчики недооценили правила сужения, что привело к ошибкам "Cannot read property ... of undefined".
История
В мобильном приложении функция принимала либо объект, либо строку. Через косвенный вызов функции, меняющей тип, сужение не происходило, и на некоторых устройствах был краш при вызове метода, отсутствующего у строки. Провалились тесты на редких кейсах.
История
При миграции кода с JavaScript на TypeScript не реализовали свои type guard функции, предполагая, что проверки на свойства сузят тип всегда. В результате сложные объекты с опциональными полями повели себя неправильно, и в рантайме возникли непредсказуемые ошибки доступа к данным.