ПрограммированиеFrontend разработчик

Поясните механику сужения типов (Type Narrowing) в TypeScript. Какие есть способы сузить типы переменных, для чего это используется?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

Сужение типа (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); } }
  • Проверка с помощью "discriminant properties" (дискриминантных свойств):
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 функции, предполагая, что проверки на свойства сузят тип всегда. В результате сложные объекты с опциональными полями повели себя неправильно, и в рантайме возникли непредсказуемые ошибки доступа к данным.