Skip to Content
TypeScripttype, interface и литералы

type, interface и литералы

Уровень: middle.

Вопрос

Когда брать interface, когда type, и почему строковый union чаще лучше enum?

Короткий ответ

Оба описывают форму объекта. interface можно дополнять через declaration merging: второе объявление с тем же именем добавляет поля. Так расширяют типы библиотек. type умеет union, intersection, кортежи и условные типы. Для пропсов компонента и моделей API разницы почти нет, в одном проекте выбирают один стиль и не мешают без причины.

enum компилируется в объект в рантайме. Строковый union type Role = 'admin' | 'user' исчезает при сборке и сужается через ===. Числовой enum ещё и даёт обратное отображение, его легко перепутать.

as const фиксирует литералы: массив становится readonly-кортежем, поле 'admin' остаётся литералом, а не string. Без этого keyof и аргументы функций размываются до широкого типа.

Разбор

Структурная типизация: объект подходит под тип, если у него есть нужные поля. Явно писать implements не обязательно. Лишнее поле мешает, когда объект передают в объектный литерал напрямую. Если значение уже лежит в переменной, лишние поля TypeScript пропускает.

readonly запрещает присваивание поля в типе. Это не Object.freeze: в рантайме поле всё ещё можно изменить из JS. Для массива readonly string[] нельзя вызвать push.

never — тип, значений у которого нет. Им помечают ветку, которая не должна выполниться, и функции, которые всегда бросают ошибку. Пустой union после Exclude тоже схлопывается в never.

const roles = ['admin', 'user'] as const type Role = (typeof roles)[number] // 'admin' | 'user'

typeof здесь берёт тип значения, не JS-оператор. Индекс [number] достаёт union элементов кортежа.

На что смотрят на senior

interface с merging удобен для declare module. В коде фичи merging случайно склеивает два разных User из разных файлов, если имя совпало и оба глобальные. Локальные типы этому не подвержены.

Перегрузки функций (overload) описывают несколько контрактов одной реализации. Их берут, когда union на входе и выходе связан парами, а не «любой вход даёт любой выход». Если хватает одного дженерика, перегрузка только шумит.

Типичные ошибки

  • Ставить enum там, где значение уходит в JSON и сравнивается со строкой с бэкенда.
  • Аннотировать константу как string и терять литерал, который нужен для switch.
  • Считать readonly защитой в рантайме.
  • Путать оператор typeof в JS и typeof в позиции типа.

Задача

Что за тип у Role и почему аннотация string[] его ломает?

const roles = ['admin', 'user'] as const type Role = (typeof roles)[number]
Показать ответ

as const делает readonly-кортеж литералов. typeof roles — тип значения, [number] достаёт элемент. Role это 'admin' | 'user'.

Аннотация const roles: string[] стирает литералы до string. Тогда Role станет string, и switch по ролям перестанет быть исчерпываемым.

Last updated on