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

Как устроен механизм типизации union types (объединённых типов) в TypeScript? Для чего он нужен, как работает сужение типа и какие есть нюансы работы с union типами?

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

Ответ.

История вопроса:

Union types появились в TypeScript для описания переменных и параметров, которые могут принимать значения разных типов. Эта возможность значительно расширила гибкость типизации по сравнению с классическими языками.

Проблема:

В JavaScript функции и переменные часто бывают многоформатными (например, принимают число или строку), что усложняет безопасную типизацию. Без union types приходилось использовать any или дублировать код. Это увеличивало количество ошибок на продакшене и затрудняло работу в больших командах.

Решение:

Union types позволяют объявлять переменную, которая может быть одного из нескольких типов, гарантированно поддерживая корректные операции после проверки. Также реализована поддержка сужения типов (type narrowing), что помогает компилятору "понять", с чем он имеет дело.

Пример кода:

function formatId(id: number | string): string { if (typeof id === 'string') { return id.toUpperCase(); } return id.toString(); }

Ключевые особенности:

  • Позволяют явно указывать возможные типы переменных и параметров.
  • Работают вместе с механизмом сужения типов через проверки (например, typeof или in).
  • Усложняют работу с объектами, где один и тот же ключ может иметь разные типы — требуется аккуратное управление логикой доступа.

Вопросы с подвохом.

Можно ли записать union из объектов с разными свойствами и обращаться к любому свойству без проверки?

Нет. Union types позволяют обращаться только к тем свойствам и методам, которые присутствуют во всех типах. Для доступа к частным свойствам требуется сужение типа.

Пример кода:

type Fish = { swim: () => void; }; type Bird = { fly: () => void; }; function move(animal: Fish | Bird) { // animal.swim(); // Ошибка без сужения if ('swim' in animal) { animal.swim(); // ОК } }

Почему не все методы доступны для union типа строка | число?

TypeScript в union разрешает только то, что есть во всех включенных типах. Для индивидуальных методов нужно сначала проверить реальный тип.

Что произойдет, если не проверять тип в union, а попытаться вызвать специфичный метод?

В таком случае возникнет ошибка компиляции, так как не гарантируется наличие метода. Работает только после проверки конкретного типа.

Типовые ошибки и анти-паттерны

  • Пропуск проверки типа перед использованием (может вызвать ошибку доступа).
  • Описание слишком широких union типов (теряется типовая безопасность).
  • Неправильное сужение приводит к невидимым ошибкам и not exhaustive checks.

Пример из жизни

Негативный кейс

Дали переменной тип string | number и без проверки сделали toUpperCase(). В результате приложение падает на числовых данных.

Плюсы:

  • Быстро написанный код.

Минусы:

  • Ошибки в рантайме.
  • Потеря доверия к статической типизации TypeScript.

Позитивный кейс

Проверяем тип перед работой с методом:

if (typeof value === 'string') { return value.toUpperCase(); } else { return value.toString(); }

Плюсы:

  • Полная безопасность на этапе компиляции.
  • Улучшенная поддерживаемость кода.

Минусы:

  • Необходимость писать лишние проверки.