Объекты и интерфейсы

Объекты и интерфейсы

Технический фундамент объектных контрактов

В TypeScript совместимость объектов обычно структурная:

  • если объект имеет нужные поля нужных типов, он совместим с интерфейсом;
  • интерфейс задает контракт данных, а не реализацию;
  • на литералах объектов работает дополнительная проверка "лишних полей" (excess property checks);
  • опциональные поля и readonly формируют стабильность модели в больших системах.

Эта модель помогает строить безопасные DTO и контракты между слоями без жесткой привязки к классам.

Почему интерфейсы важны

В реальном приложении основная сложность часто не в отдельных переменных, а в структуре объектов: пользователь, заказ, настройки, ответ API. Если структура не зафиксирована, баги появляются на уровне "поля нет" или "тип поля не тот". Интерфейсы в TypeScript задают четкий контракт формы объекта.

Ключевой момент: интерфейс описывает "что должно быть" в объекте, а не "как это реализовано".

Проверь себя: почему контракт User полезнее набора комментариев над кодом?

Базовый синтаксис интерфейса

interface User {
  id: number;
  name: string;
  email: string;
}

const user: User = {
  id: 1,
  name: 'Anna',
  email: 'anna@mail.com',
};

Смотри, что важно: если поле отсутствует или тип не совпадает, TypeScript покажет ошибку сразу.

Лишние поля и excess property checks

Проверка "лишних полей" особенно заметна на литералах объектов: TypeScript пытается поймать опечатки и случайные поля в месте создания объекта.

interface UserBase {
  id: number;
  name: string;
}

const u1: UserBase = { id: 1, name: 'Anna', role: 'admin' };
//              ^ Ошибка: у литерала объекта есть лишнее поле "role"

const raw = { id: 1, name: 'Anna', role: 'admin' };
const u2: UserBase = raw; // OK: у переменной "raw" просто более широкая структура

Смотри, что важно: это не "противоречие", а дополнительная защита именно на литералах объектов. Она помогает ловить ошибки вроде { nmae: '...' } вместо { name: '...' }.

Необязательные и readonly-поля

Не все поля обязательны в каждом сценарии.

interface Profile {
  id: number;
  name: string;
  avatarUrl?: string;
  readonly createdAt: string;
}
  • ? делает поле опциональным.
  • readonly запрещает изменять поле после инициализации.

Это особенно полезно для данных, которые приходят с сервера и не должны мутироваться на клиенте.

Смотри, что важно: readonly в TypeScript работает на этапе компиляции и обычно "поверхностно" (shallow) - оно запрещает переназначить поле, но не делает автоматически неизменяемыми вложенные объекты.

interface Profile {
  readonly settings: { theme: string };
}

const p: Profile = { settings: { theme: 'dark' } };
// p.settings = { theme: 'light' }; // Ошибка: settings readonly
p.settings.theme = 'light'; // OK: вложенное поле не readonly

Интерфейсы для функций

Интерфейс может описывать сигнатуру функции.

interface FormatPrice {
  (value: number, currency: string): string;
}

const formatPrice: FormatPrice = (value, currency) => `${value} ${currency}`;

Новый термин: сигнатура функции - описание параметров и возвращаемого типа.

Проверь себя: зачем задавать интерфейс функции, если TypeScript и так умеет вывести тип?

Расширение интерфейсов

Интерфейсы можно расширять через extends.

interface User {
  id: number;
  name: string;
}

interface Admin extends User {
  role: 'admin';
  permissions: string[];
}

Это помогает строить иерархию моделей без дублирования полей.

Реальные микро-сценарии

  1. Ответ API.

Интерфейс ApiResponse<T> фиксирует структуру data, error, meta.

  1. Конфиг компонента.

Интерфейс пропсов задает обязательные и опциональные параметры.

  1. Модель заказа.

Интерфейс гарантирует, что у заказа есть id, items, status, total.

Частые ошибки новичков

  • Делать интерфейсы слишком широкими (field?: any почти везде).
  • Копировать похожие интерфейсы вместо расширения.
  • Путать интерфейс и конкретный класс-объект.
  • Игнорировать readonly и случайно мутировать критичные данные.

Анти-провал: если объект используется в нескольких местах, сначала стабилизируй его интерфейс, и только потом расширяй функциональность.

Что будет, если изменить входные данные

Если User.email неожиданно станет null, а интерфейс требует string, TypeScript заставит тебя явно обработать этот случай. Это предотвращает скрытые падения, например при вызове строковых методов на null.

Проверь себя: когда лучше использовать email: string | null, а когда оставить строго string?

Краткий итог

  • Интерфейсы задают контракт формы объектов и функций.
  • Они помогают фиксировать структуру данных и предотвращать ошибки на границе модулей.
  • Опциональные и readonly поля делают модель гибкой и безопасной.
  • extends позволяет наращивать модель без дублирования.
  • Четкие интерфейсы ускоряют разработку команды и упрощают рефакторинг.