ЯдроКодаподготовка к экзаменам
Учебная платформа

Загружаем материалы

Подготавливаем материалы и навигацию по разделу.

Type и interface в TypeScript: отличия и описание данных из JSON

Автор: · Обновлено

type и interface позволяют назвать форму объекта, но решают не полностью одинаковые задачи. На примере расписания разберём выбор конструкции, объединение деклараций и перевод…

type и interface позволяют назвать форму объекта, но решают не полностью одинаковые задачи. На примере расписания разберём выбор конструкции, объединение деклараций и перевод JSON в описание данных, которое не обещает лишнего.

Цель и предпосылки

Вам понадобятся объекты JavaScript, массивы и базовые аннотации параметров. К концу урока вы сможете описать знакомый JSON, объяснить разницу между необязательным полем и null, выбрать type для объединения вариантов и заметить конфликт при расширении модели. Проверка исходящего JavaScript и валидация неизвестного документа остаются отдельными действиями.

От одного документа к договорённости

Вот пример ответа сервиса расписаний. Названия полей и значения выбраны для учебного случая; единственный ответ ещё не является полной спецификацией. По нему видно, что slots — массив объектов, но не видно, бывает ли массив пустым, отсутствует ли room и какие ещё состояния поддерживает сервер. Генератор «JSON to TypeScript interface» может предложить начальную форму. Ответы о допустимых данных всё равно нужно получить из контракта или нескольких осмысленных примеров.

{"code":"ts-evening","room":null,"slots":[{"day":"tuesday","minutes":90}]}

В следующем самостоятельном примере исходный документ представлен знакомым объектом, поэтому его можно проверить при компиляции. Мы намеренно описываем room как обязательное поле, допускающее null. Это означает «комната пока не назначена», а не «мы забыли передать информацию о комнате». Если протокол действительно разрешает отсутствие, запись будет другой: room?: string | null.

export {};

interface ClassSlot {
  day: string;
  minutes: number;
}
interface ClassSchedule {
  code: string;
  room: string | null;
  slots: ClassSlot[];
}

type ScheduleSummary = {
  code: ClassSchedule['code'];
  slotCount: number;
};

const schedule: ClassSchedule = {
  code: 'ts-evening',
  room: null,
  slots: [{ day: 'tuesday', minutes: 90 }],
};
const summary: ScheduleSummary = {
  code: schedule.code,
  slotCount: schedule.slots.length,
};
console.log(JSON.stringify(summary));

if (false) {
  // @ts-expect-error room обязательно, хотя его значением может быть null.
  const incomplete: ClassSchedule = { code: 'ts-evening', slots: [] };
  void incomplete;
}

Программа выводит {"code":"ts-evening","slotCount":1}. Никакое объявление интерфейса не создало объект автоматически: данные созданы литералом, а сводка — отдельным выражением. Индексный доступ ClassSchedule['code'] сохраняет связь с исходной моделью. Если код расписания изменит тип, зависимое поле обновится вместе с ним без ручной правки второй декларации.

Где конструкции расходятся

Для той же формы расписания допустима запись type ClassSchedule = { ... }. Однако псевдоним может также обозначать строковый литерал, объединение, кортеж или результат преобразования типа. Например, состояние бронирования удобно представить как 'open' | 'closed'. Интерфейс описывает объектную форму и может расширяться через extends; переоткрытие одноимённого интерфейса позволяет дополнить его декларацию.

Слияние не означает, что любые одинаковые имена во всех файлах проекта автоматически становятся одним типом. Важна область видимости: объявления в независимых модулях не склеиваются только из-за совпадения названия. Дополнение чужого модуля — осознанная операция с declare module. В учебных файлах export {} делает файл модулем и помогает избежать случайных пересечений с глобальными DOM-именами.

У расширения и пересечения есть полезное различие при конфликте полей. Если интерфейс должен наследовать несовместимые определения одного свойства, ошибка появляется при объявлении расширения. Пересечение & требует удовлетворить обеим сторонам сразу; отдельное конфликтное поле может стать never, и проблема обнаружится при попытке создать значение. Поэтому & нельзя воспринимать как операцию перезаписи поля справа налево, аналогичную разворачиванию объектов.

Что не умеет преобразователь JSON

Автоматический вывод по [] не знает тип будущих элементов. По одному значению null нельзя понять, должен ли здесь позже появиться адрес, число или вложенный объект. По строке с датой нельзя без договора заключить, что все строки проходят календарную проверку. Не следует превращать все наблюдаемые строковые значения в литеральные типы: один идентификатор записи обычно представляет открытое множество идентификаторов, а не единственный допустимый идентификатор.

Особенно опасна конструкция JSON.parse(text) as ClassSchedule. Она не проверяет ни существование slots, ни числовой тип minutes. При обработке внешнего текста сначала присвойте результат переменной unknown, затем выполните проверку выбранного контракта. Подробный разбор этой границы находится в уроках о JSON и runtime validation. Здесь задача — правильно сформулировать саму модель, которую предстоит проверять.

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

Не смешивайте room?: string и room: string | null: первая запись допускает отсутствие свойства, вторая требует присутствия с одним из двух значений. Не используйте interface только ради слова «интерфейс» в запросе к генератору. Для union нужен псевдоним, а для расширяемой объектной декларации может подходить интерфейс. Соглашение команды полезнее искусственного правила, требующего одной конструкции во всех ситуациях.

Практикум: проверьте вывод по неполным данным

Получены три документа: первый с room: null, второй с room: 'B-204', третий без room; у всех есть code, а slots иногда пуст. Составьте вопросы владельцу API и два возможных контракта. Затем решите, как описать объект для списка, которому нужны только код и число занятий. Не добавляйте десяток необязательных свойств на всякий случай: такая модель перестаёт объяснять допустимые состояния.

Разбор: если отсутствие комнаты — ошибка старой версии сервера, основной контракт остаётся с обязательным room; неверный документ следует обработать отдельно. Если отсутствие официально разрешено, нужен room?: string | null, и потребитель должен различать выбранные значения осмысленно. Тип элемента slots выводят из непустых примеров и спецификации, а не из пустого массива. Для краткой карточки создают отдельную производную модель: массив занятий не требуется копировать в каждый компонент. Проверьте решения примерами с отсутствием, null, строкой и числом вместо комнаты.

Частые вопросы

Удалит ли Omit лишние свойства из разобранного JSON?

Нет. Полученный тип ограничивает видимую форму при проверке программы, но не выполняет преобразование объекта. Если в ответе нельзя оставлять поле, создайте новый объект с выбранными значениями или используйте явно настроенный парсер. Поведение во время выполнения проверяйте независимо от названия TypeScript-типа.

Достаточно ли одного JSON для окончательного интерфейса?

Одного документа достаточно для чернового описания наблюдаемого экземпляра, но обычно недостаточно для договора. Нужно уточнить обязательность, допустимые варианты, ограничения чисел, пустые коллекции и развитие формата. Иначе автоматически полученный тип будет случайно слишком узким либо чрезмерно разрешающим.

Источники