Разработка программного обеспечения
Интерфейс TypeScript и тип: Реальные различия, протестированные с помощью компилятора
Интерфейсы и псевдонимы типов настолько перекрываются, что выбор часто кажется произвольным. Вот что каждый может сделать, а другой нет, что подтверждается реальными ошибками компилятора, а также простое правило, которому может следовать ваша команда.
- Для простых форм объектов интерфейс и псевдоним типа практически взаимозаменяемы.
- Только
typeможет называть объединения, кортежи и другие необъектные типы. - Только
interfaceподдерживает слияние объявлений, когда два объявления объединяются. - Общее правило: интерфейсы для форм объектов,
typeдля всего остального, применяемые последовательно.
Лишь немногие вопросы о TypeScript вызывают столько споров, как Интерфейс TypeScript и тип. Во многих статьях перечислены различия, которые раньше имели значение в старых версиях, но теперь устарели. Поэтому вместо того, чтобы повторять утверждения, мы проверили их.
Мы написали по одному файлу, реализующему каждое различие, и проверили его с помощью TypeScript 5.6.3 в строгом режиме. Ошибки компилятора, приведенные ниже, происходят из-за этого запуска. Поведение может меняться в зависимости от версии, поэтому проверьте версию TypeScript, которую использует ваш проект. Если вы новичок в этом языке, начните с нашего учебника по TypeScript для начинающих.
Короткий ответ
interface описывает форму объекта и может быть расширен или открыт повторно. Псевдоним type дает имя любому типу, включая объекты, объединения, кортежи и типы функций. Там, где могут работать оба, разница в основном заключается в стиле.
Где они одинаковы
Эти два объявления описывают идентичные формы:
interface User {
id: number;
name: string;
email?: string;
}
type UserAlias = {
id: number;
name: string;
email?: string;
};
const a: User = { id: 1, name: "Asha" };
const b: UserAlias = a; // no error
TypeScript использует структурную типизацию, что означает, что он сравнивает формы, а не имена. Вот почему User можно без проблем назначить на UserAlias. Оба поддерживают дополнительные свойства, readonly свойства, методы и сигнатуры индексов.
Интерфейс и тип рядом
| Особенность | интерфейс | тип |
|---|---|---|
| Формы объектов | Да | Да |
Союзы, такие как "a" | "b" |
Нет | Да |
| Кортежи и примитивы | Нет | Да |
| Сопоставленные и условные типы | Нет | Да |
| Объединение деклараций | Да | Нет, это ошибка |
| Расширение | простирается |
& пересечение |
| Противоречивые свойства | Ошибка в декларации | Может спокойно стать never |
Используется с implements |
Да | Да, если это тип объекта |
Что может только тип
Многие полезные типы вообще не являются фигурами объектов, и их можно назвать только псевдонимом типа:
type Status = "pending" | "paid" | "shipped"; // union
type Point = [number, number]; // tuple
type Handler = (event: string) => void; // function type
type Keys = keyof User; // computed type
type ReadonlyUser = Readonly<User>; // utility type
Типы объединения являются одной из наиболее ценных функций TypeScript, поскольку компилятор отклоняет любое значение за пределами списка:
let s: Status = "paid";
s = "refunded";
// error TS2322: Type '"refunded"' is not assignable to type 'Status'.
Это позволяет обнаружить опечатки и устаревшие значения во время компиляции, прежде чем они попадут в рабочую среду.
Объединение деклараций
Если вы дважды объявите интерфейс с одним и тем же именем, TypeScript объединит их в один:
interface Settings {
theme: string;
}
interface Settings {
language: string;
}
const settings: Settings = { theme: "dark", language: "en" }; // no error
Попробуйте то же самое с псевдонимом типа, и компилятор откажет:
type Config = { theme: string };
type Config = { language: string };
// error TS2300: Duplicate identifier 'Config'.
Слияние — это то, как библиотеки позволяют расширять свои типы. Например, вы можете добавить пользовательское свойство в глобальный интерфейс Window или в объект запроса платформы, не изменяя библиотеку. Это действительно полезно.
Обратной стороной является то, что слияние может произойти случайно. Если два файла в проекте объявляют интерфейс с одинаковым именем в одной области, они автоматически объединяются, что может сбить с толку. Вместо этого псевдоним типа делает это столкновение ошибкой.
расширяется против пересечений
Оба позволяют использовать существующий тип:
interface Admin extends User {
permissions: string[];
}
type AdminAlias = UserAlias & { permissions: string[] };
Для совместимых свойств результат тот же. Разница проявляется в конфликте. Когда интерфейс пытается изменить тип свойства несовместимым образом, компилятор сразу сообщает об этом:
interface Base { id: number }
interface Broken extends Base { id: string }
// error TS2430: Interface 'Broken' incorrectly extends interface 'Base'.
// Types of property 'id' are incompatible.
// Type 'string' is not assignable to type 'number'.
Перекресток не вызывает жалоб при его объявлении. Вместо этого конфликтующее свойство становится never, тип которого не может иметь ни одно значение. Ошибка появляется только позже, когда вы пытаетесь ее использовать:
type Mixed = { id: number } & { id: string };
const m: Mixed = { id: 1 };
// error TS2322: Type 'number' is not assignable to type 'never'.
Эта более поздняя ошибка еще больше сбивает с толку, поскольку она указывает на настоящую ошибку. Это одна из практических причин отдать предпочтение extends при создании типов объектов.
Классы и орудия
Класс может обещать соответствовать форме, используя implements, и это работает с интерфейсом или псевдонимом типа объекта:
class Account implements User {
constructor(public id: number, public name: string) {}
}
Это скомпилировано без ошибок. Обратите внимание, что implements проверяет только соответствие класса. Он не добавляет никаких свойств или кода. Класс не может реализовать тип объединения, поскольку не существует единой формы, которой можно было бы соответствовать.
Только чтение и типы утилит
Типы утилит, такие как Readonly, Partial, Pick и Omit, работают как с интерфейсами, так и с псевдонимами. Результатом является новый тип, которому обычно присваивается псевдоним типа:
const r: ReadonlyUser = { id: 3, name: "Dev" };
r.name = "Chitra";
// error TS2540: Cannot assign to 'name' because it is a read-only property.
Помните, что readonly — это проверка только во время компиляции. Во время выполнения объект представляет собой обычный JavaScript, и его по-прежнему можно изменить с помощью кода, обходящего типы.
Совет. Типы исчезают, когда TypeScript компилируется в JavaScript. Данные, поступающие извне, например ответ API, не проверяются во время выполнения только потому, что у них есть тип. Проверьте это, например, с помощью библиотеки схем. В нашем руководстве по Fetch API рассматривается безопасная обработка ответов API.
Производительность компилятора
Собственные рекомендации по производительности команды TypeScript предлагают отдавать предпочтение интерфейсам, использующим extends, а не большим пересечениям, поскольку компилятор может более эффективно кэшировать отношения между интерфейсами. В малых и средних проектах вы вряд ли заметите какую-либо разницу. Это может иметь значение в очень больших базах кода с глубоко составленными типами. Мы сами это не измеряли.
Что вам следует использовать?
Широко используемое правило, соответствующее рекомендациям руководства по TypeScript, гласит:
- Используйте
interfaceдля фигур объектов, особенно общедоступных API и всего, что реализуют классы или могут расширять другие. - Используйте
typeдля объединений, кортежей, типов функций, а также отображаемых или вычисляемых типов.
| Вы определяете | Использование |
|---|---|
| Модель данных, например Пользователь или Заказ. | интерфейс |
| Набор разрешенных значений | type союз |
| Пара фиксированной длины или тройка | type кортеж |
| Подпись обратного вызова | тип |
| Расширение типов библиотеки | interface путем слияния |
| Вариант существующего типа | type с типом утилиты |
Некоторые команды просто везде используют type, и это тоже хорошо работает. Самое главное — это последовательность, поэтому выберите соглашение, запишите его в своем руководстве по стилю и позвольте линтеру обеспечить его соблюдение. Правило ESLint consistent-type-definitions из typescript-eslint позволяет это сделать.
Распространенные ошибки
- Бесконечные дебаты. Оба варианта хороши для объектов, поэтому установите правило один раз и двигайтесь дальше.
- Случайное слияние. Повторное использование имени интерфейса в одной области незаметно объединяет их.
- Конфликтующие пересечения. Позже они создают
neverтипы с запутанными ошибками. - Доверительные типы во время выполнения. Они удаляются, поэтому проверяйте внешние данные.
- Использование
anyдля отключения ошибок. Отключает проверку. Отдайте предпочтениеunknownи сузьте его.
Переключение между ними в существующей кодовой базе
Изменение interface на type или наоборот обычно безопасно для простых фигур объектов, поскольку TypeScript сравнивает типы по их структуре, а не по именам. Перед переключением все еще необходимо проверить несколько вещей:
- Преобразование интерфейса в псевдоним типа устраняет слияние объявлений, поэтому любое второе объявление с тем же именем становится ошибкой дублирующегося идентификатора.
- Преобразование псевдонима типа в интерфейс работает только для фигур объектов. Объединения, кортежи и отображаемые типы должны оставаться псевдонимами типов.
- Сообщения об ошибках и при наведении курсора на редактор могут отображать тип по-разному, что может удивить товарищей по команде, но не меняет поведения.
Внесите изменение в одном целенаправленном запросе на включение, запустите компилятор в строгом режиме и свой набор тестов и избегайте смешивания его с работой над функциями, чтобы рецензенты могли видеть, что больше ничего не изменилось.
Итоги
На вопрос «интерфейс против типа» есть спокойный ответ: они перекрываются для объектов, у каждого есть несколько уникальных способностей, и последовательность важнее, чем выбор. Используйте интерфейсы для форм и типов для всего остального, если ваша команда не предпочитает иное. Чтобы увидеть различия самостоятельно, скопируйте приведенные выше примеры в файл и запустите на нем tsc --noEmit --strict.
Часто задаваемые вопросы
В чем разница между интерфейсом и типом в TypeScript?
Оба могут описывать формы объектов. Только тип может называть объединения, кортежи и вычисляемые типы, тогда как только интерфейс поддерживает слияние объявлений.
Должен ли я использовать интерфейс или тип для реквизитов React?
Либо работает хорошо. Многие команды используют интерфейсы для реквизитов, в то время как другие предпочитают шрифты для согласованности с объединениями. Выберите один и примените его на протяжении всего проекта.
Может ли тип расширять интерфейс?
Да, используя пересечение, например, типа Admin = User & { Permissions: string[] }. Интерфейс также может расширять псевдоним типа объекта.
Интерфейс быстрее типа?
Рекомендации по производительности TypeScript отдают предпочтение расширению интерфейса на больших пересечениях в больших базах кода. В типовых проектах разница вряд ли будет заметна.
Что такое объединение деклараций?
Это когда два интерфейса с одинаковым именем в одной области объединяются в один. Псевдонимы типов не могут объединяться, а повторяющиеся имена вызывают ошибку.
Продолжайте учиться
Нужна помощь с тестированием, WordPress или SEO?
Мы помогаем командам выпускать лучшее программное обеспечение и развивать свои сайты. Расскажите нам, над чем вы работаете, и мы предложим четкий следующий шаг.
Больше руководств по тестирование, искусственный интеллект и современные разработки
Понравилась эта статья? Выберите тему и сразу приступайте к нашим новейшим руководствам.
Объяснение галлюцинаций искусственного интеллекта: почему языковые модели все выдумывают и как их уменьшить
Галлюцинации ИИ объяснили: что это такое, почему языковые модели их создают, распространенные типы, как уменьшить их с помощью заземления и подсказо…
Читать статью →DevOpsОбъяснение переменных среды: файлы .env, секреты и правильно выполненная конфигурация
Переменные среды объясняются на примерах Python, которые мы запускали: их чтение и установка, почему каждое значение является строкой, файлы .env, н…
Читать статью →Разработка ПООбъяснение ограничения скорости API: алгоритмы, заголовки и обработка ошибок 429
Ограничение скорости API объясняется с помощью проведенного нами моделирования: сегмент токенов, фиксированное окно, скользящее окно, 429 ответов, з…
Читать статью →Автоматизация тестированияИздевательство в Python: unittest.mock, патч и ошибки, из-за которых тесты лгут
Издевательство на Python с помощью unittest.mock, протестировано: Mock и return_value, Side_effect, исправление использования имени, спецификация и …
Читать статью →Тестирование ПООбъяснение покрытия кода: покрытие линий или ветвей и что на самом деле означает 100%
Покрытие кода объяснено с помощью реального запуска покрытия.py: покрытие строк, ветвей и условий, чтение отчета, почему 100%-ное покрытие все еще м…
Читать статью →Базы данных и SQLDELETE, TRUNCATE и DROP в SQL: различия, откат и когда использовать каждый из них
Объяснение DELETE, TRUNCATE и DROP на примере протестированного SQL: что каждый из них удаляет, откат, триггеры, сброс идентификаторов, скорость и ч…
Читать статью →Базы данных и SQLОбъяснение представлений SQL: создавайте, используйте и избегайте ловушек, с примерами
Представления SQL, объясненные с помощью запросов, которые мы выполняли: создание представления, результаты в реальном времени, скрытие столбцов, пр…
Читать статью →Разработка ПОИнтерфейс TypeScript и тип: реальные различия, протестированные с помощью компилятора
Интерфейс TypeScript против типа, проверенный реальным компилятором: что могут делать оба, что может делать только один, слияние объявлений, расшире…
Читать статью →Разработка ПООбъяснение Python *args и **kwargs: гибкие функции с понятными примерами
Python *args и **kwargs, объясненные с помощью кода, который мы запускали: упаковка и распаковка, порядок параметров, аргументы только по ключевым с…
Читать статью →Разработка ПОЛямбда-функции Python: синтаксис, реальное использование и когда их не следует использовать
Лямбда-функции Python, объясненные с помощью кода, который мы запускали: синтаксис, сортировка с помощью ключа, карты и фильтра, замыкания, ловушка …
Читать статью →Искусственный интеллектЧто такое ИИ-агенты? Как они работают: можно запустить простой цикл агента
Что такое агенты ИИ? Простое руководство по циклу агента, инструментам, памяти и ограничениям, с небольшим циклом Python, который мы запустили, реал…
Читать статью →DevOpsМониторинг и наблюдаемость: реальная разница с журналами, метриками и трассировками
Объяснение мониторинга и наблюдаемости: известные и неизвестные сбои, журналы, метрики и трассировки, пошаговое руководство по инцидентам при оформл…
Читать статью →DevOpsDocker Volumes против Bind Mounts: правильное сохранение данных контейнера
Объяснение томов Docker и монтирования привязки: почему исчезают данные контейнера, именованные тома, монтирование привязки, tmpfs, примеры создания…
Читать статью →Автоматизация тестированияTDD против BDD: ключевые различия, одна функция, созданная для обоих способов, и когда использовать каждый
TDD и BDD объясняются с помощью одной функции скидки, созданной в обоих направлениях: красно-зеленый рефакторинг с кодом, который мы запускали, сцен…
Читать статью →Разработка ПОGit merge vs rebase: отличия, примеры и когда что использовать
Git merge vs rebase с реальным выводом log, сравнительной таблицей, золотым правилом rebase и безопасным рабочим процессом для команды.
Читать статью →Разработка ПОГенераторы списков в Python: синтаксис, примеры и когда их избегать
Генераторы списков в Python: синтаксис, фильтры, if-else, вложенные циклы, измеренное время и случаи, когда обычный цикл for лучше.
Читать статью →Базы данных и SQLSQL JOIN: объяснение с примерами (Inner, Left, Right, Full)
Разберите SQL JOIN шаг за шагом на примерах таблиц и запросов: INNER, LEFT, RIGHT, FULL, CROSS и SELF, а также частые ошибки и советы по производите…
Читать статью →