Объекты и интерфейсы
Объекты и интерфейсы
Технический фундамент объектных контрактов
В 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[];
}
Это помогает строить иерархию моделей без дублирования полей.
Реальные микро-сценарии
- Ответ API.
Интерфейс ApiResponse<T> фиксирует структуру data, error, meta.
- Конфиг компонента.
Интерфейс пропсов задает обязательные и опциональные параметры.
- Модель заказа.
Интерфейс гарантирует, что у заказа есть id, items, status, total.
Частые ошибки новичков
- Делать интерфейсы слишком широкими (
field?: anyпочти везде). - Копировать похожие интерфейсы вместо расширения.
- Путать интерфейс и конкретный класс-объект.
- Игнорировать
readonlyи случайно мутировать критичные данные.
Анти-провал: если объект используется в нескольких местах, сначала стабилизируй его интерфейс, и только потом расширяй функциональность.
Что будет, если изменить входные данные
Если User.email неожиданно станет null, а интерфейс требует string, TypeScript заставит тебя явно обработать этот случай. Это предотвращает скрытые падения, например при вызове строковых методов на null.
Проверь себя: когда лучше использовать email: string | null, а когда оставить строго string?
Краткий итог
- Интерфейсы задают контракт формы объектов и функций.
- Они помогают фиксировать структуру данных и предотвращать ошибки на границе модулей.
- Опциональные и
readonlyполя делают модель гибкой и безопасной. extendsпозволяет наращивать модель без дублирования.- Четкие интерфейсы ускоряют разработку команды и упрощают рефакторинг.