В отличие от многих объектно-ориентированных языков, TypeScript реализует структурную типизацию (duck typing): объект считается соответствующим типу, если у него есть все необходимые свойства, независимо от того, явно ли он объявлен этого типа.
Эта гибкость иногда приводит к неожиданному принятию объектов за тип, если структура совпадает, хотя на самом деле они не связаны по смыслу. Это опасно при сложных моделях данных, когда структура совпадает случайно.
Всегда правильно структурируйте типы объектов, минимизируйте совпадения структур разных сущностей, для критичных случаев используйте дополнительные свойства или символы для «номинализации» типов.
Пример кода:
interface Point { x: number; y: number; } interface Pixel { x: number; y: number; } function drawPoint(p: Point) { console.log(p.x, p.y); } const pixel: Pixel = { x: 1, y: 2 }; drawPoint(pixel); // OK, типы совместимы структурно
Ключевые особенности:
Если структура двух интерфейсов совпадает, значит ли это, что они полностью взаимозаменяемы?
Возможно по структуре, но не по логике программы. Это допустимо на уровне компилятора, но может привести к логическим ошибкам (например, Point и Pixel выше).
Можно ли запретить структурную совместимость для некоторого типа?
Полностью нет, но можно добавить уникальное свойство (например, с символом):
interface Brand { _brand: unique symbol; }
Теперь другой объект не сможет имитировать этот тип без такого же уникального символа.
Чем структурная типизация отличается от номинативной?
Структурная — по наличию структуры, номинативная — по совпадению имени типа в определённом namespace. В TS по умолчанию всегда structural typing.
В ряде сущностей unintentionally совпали поля (например, User и Admin как {id: number, name: string}), что привело к путанице при работе с контрактами API.
Плюсы:
Минусы:
Использование уникальных «меток» символов и нестандартных полей для разграничения семантически разных типов с одинаковой структурой.
Плюсы:
Минусы: