Чем интерфейс отличается от типа typescript
Перейти к содержимому

Чем интерфейс отличается от типа typescript

  • автор:

Разница между типом и интерфейсом в TypeScript

Тип и интерфейс в TypeScript часто вызывают путаницу по причине поверхностной схожести. Ситуацию усугубляют устаревшие статьи, необъективные сравнения и style-guid’ы некоторых фреймворков. Например, в Angular по умолчанию включено tslint правило interface-over-type-literal , которое требует использовать интерфейсы вместо типов везде, где возможно. В этой статье разберём разницу между типом и интерфейсом в TypeScript и определимся что же использовать.

Сходства

Интерфейсы и типы могут использоваться для описания структур данных:

type Employee = < salary: number; >// Или interface Employee

Могут использоваться для типизации функции:

interface CalculateSalary < (employee: Employee): number; >// Или type CalculateSalary = (employee: Employee) => number;

Интерфейсы и типы могут быть имплементированы классами:

type Employee = < giveEstimate(task: Task): number; >// Или interface Employee < giveEstimate(task: Task): number; >class YoungDeveloper implements Employee < giveEstimate(task: Task): number < return task.complexity * 0.01; >> class MatureDeveloper implements Employee < giveEstimate(task: Task): number < return task.complexity * random(10, 1000) * Math.PI; >>

Интерфейсы и типы позволяют выразить пересечение типов:

type TwitterProfile = Photographer & Musician & Entrepreneur & CoffeeDrinker; // Или interface TwitterProfile extends Photographer, Musician, Entrepreneur, CoffeeDrinker <>;

Отличие 1 — Mapped типы

Интерфейсы нельзя комбинировать с mapped типами (Required, Pick, Readonly, Partial и прочими):

// С типом работает type RealProfile = Pick; // С интерфейсом не работает interface RealProfile extends Pick <>;

Интерфейс может расширять только интерфейсы, классы или другие типы, поэтому код нужно переписать так:

type OnlyDrinksCoffee = Pick; interface RealProfile extends OnlyDrinksCoffee <>

По этой же причине только с помощью типа можно требовать, чтобы все свойства были обязательны или наоборот опциональны:

type TraineeDeveloper = Partial> // Может существовать без ЗП, еды и сна const trainee: TraineeDeveloper = <>

Отличие 2 — Union

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

type Wish = | < fast: true, quality: true, cheap: false >// Дорого | < fast: true, quality: false, cheap: true >// Некачественно | < fast: false, quality: true, cheap: true >// Медленно const wish: Wish = < fast: true, quality: true, cheap: true >// Не компилируется

Ещё интерфейсы не могут расширять union типы.

Отличие 3 — Declaration merging

Интерфейсы поддерживают declaration merging — слияние интерфейсов с одинаковыми именами:

interface Employee < salary: number; >interface Employee < age: number; >const employee: Employee = < age: 23 >; // Ошибка компиляции, так как не выдали salary

Эту особенность можно использовать если тайпинги сторонней библиотеки устарели, а вам нужно расширить интерфейс недостающими свойствами и методами. Если вы делаете свою библиотеку, то тут вам решать, стоит ли давать такие точки расширения функциональности. Если библиотека на TypeScript — то не стоит, так как декларации типов на выходе всегда будут актуальными и пользователю не понадобится исправлять расхождения между типами и рантаймом. Сейчас всё больше библиотек пишутся сразу на TypeScript или поставляются с тайпингами, поэтому необходимость использовать declaration merging возникает всё реже. Вдобавок исправить в устаревшем интерфейсе можно далеко не всё — нельзя удалить свойство или поменять тип существующего:

interface Employee < salary: number; >interface Employee < salary?: number; >const employee: Employee = <>; // Ошибка, Employee всё ещё требует salary

Отличие 4 — Рекурсивные типы

До TypeScript 3.7 были отличия в том, как работают рекурсивные типы и интерфейсы. С помощью типов нельзя было типизировать рекурсивные структуры, однако в новых версиях языка проблемы нет. Подробности в release notes языка.

Отличие 5 — Совместимость с типом Record

Из-за того, что interface поддерживает declaration merging (может быть расширен в любом месте) его нельзя использовать там, где ожидается Record . Это может быть проблемой в ситуациях, где нужно использовать URLSearchParams или другое браузерное API, ожидающее Record

interface Employee < name: string; >const employee: Employee = < name: '' >new URLSearchParams(employee);

Этот код не скомпилируется с интерфейсом, зато будет работать если интерфейс поменять на тип.

Итог

Типы являются более предпочтительным вариантом, так как вы можете заменить интерфейсы типами, но не наоборот. Для использования продвинутой функциональности TypeScript — mapped типов, объединения типов и условных типов интерфейсы не подойдут.

Примитивные типы

Псевдонимы типов могут определять примитивные типы (символ, булевое значение, строку, число, bigint и т. д.), в то время как интерфейсы не могут.

Обратите внимание на то, что псевдоним типа не создает новых типов (отсюда и его название). Интерфейсы, напротив, всегда создают новые типы.

Типы-объединения

type Fruit = ‘apple’ | ‘lemon’;type Vegetable = ‘potato’ | ‘tomato’;// ‘apple’ | ‘lemon’ | ‘potato’ | ‘tomato’type Food = Fruit | Vegetable;const apple: Food = ‘apple’;>

Типы-объединения также можно определить только с помощью псевдонимов типов.

Типы-кортежи

type Animal = [name: string, age: number];const cat: Animal = [‘’, 1];>

Типы-кортежи также можно определить только с помощью псевдонимов типов.

Типы объектов/функций

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

// При использовании интерфейсов одинаковые интерфейсы автоматически объединяются.interface PrintName (name: string): void;>interface PrintName (name: number): void;>// ✅const printName: PrintName = (name: number | string) => console.log(‘name: ‘, name);>;>// При использовании псевдонимов типов они должны быть уникальными, для пересечения можно использовать только `&`.type PrintName = ((name: string) => void) & ((name: number) => void);// ✅const printName: PrintName = (name: number | string) => console.log(‘name: ‘, name);>;>

Другим ключевым моментом является то, что псевдонимы типов используют пересечение, а интерфейсы — наследование.

interface Parent printName: (name: number) => void;>// ❌ Интерфейс 'Child' некорректно расширяет интерфейс 'Parent'.interface Child extends Parent printName: (name: string) => void;>>type Parent = printName: (name: number) => void;>;type Child = Parent & // Здесь два printName будут пересекаться.// Это эквивалентно`(name: number | string) => void`printName: (name: string) => void;>;const test: Child = printName: (name: number | string) => console.log(‘name: ‘, name);>,>;test.printName(1);test.printName(‘1’);>

Как показано в ошибке выше, когда интерфейс наследуется, подтип не может конфликтовать с супертипом, его можно только расширить:

interface Parent printName: (name: number) => void;>interface Child extends Parent // ✅printName: (name: string | number) => void;>>

Интерфейс использует extends для реализации наследования, а псевдоним типа — & для реализации пересечения.

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

Поэтому следуйте такому совету: если хотите объявить тип объекта, используйте сначала интерфейс, а при необходимости — псевдонимы типов.

Сопоставленные типы объектов

type Vegetable = ‘potato’ | ‘tomato’;type VegetableOption = [Property in Vegetable]: boolean;>;const option: VegetableOption = potato: true,tomato: false,>;// “potato” | “tomato”type VegetableAlias = keyof VegetableOption;>interface VegetableOption // ❌ Сопоставленный тип не может объявлять свойства или методы.[Property in Vegetable]: boolean;>>export <>;

Сопоставленные типы объектов могут быть определены только с помощью псевдонимов типов, включая ключевые слова in и keyof .

Неизвестные типы

const potato = < name: ‘potato’, weight: 1 >;// тип Vegetable = // имя: string;// вес: number;// >type Vegetable = typeof potato;const tomato: Vegetable = name: ‘tomato’,weight: 2,>;>

При работе с неизвестными типами можно применять typeof для захвата типа, но в этом случае допустимы только псевдонимы типов, а не интерфейсы.

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

  • Замечательные новые фичи TypeScript 3.5
  • Индексация строк в Rust и TypeScript в сравнениях
  • 7 инструментов для разработки веб-компонентов

TypeScript: Сравнение Типа и Интерфейса

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

Ключевые моменты

  • Понимание различий между типами и интерфейсами TypeScript очень важно для принятия обоснованных решений при разработке.
  • Типы предпочтительнее использовать для примитивных значений, типов объединения/пересечения, функций и кортежей, в то время как интерфейсы обеспечивают лучшие сообщения об ошибках и производительность проверки типов.
  • Выбор между ними должен основываться на личных предпочтениях, удобстве чтения и требованиях проекта.

Понимание типа и интерфейса TypeScript

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

Интерфейсы могут быть расширены дополнительными функциями после их первоначального объявления, что отличает их от типов и псевдонимов/алиасов типов в TypeScript.

Типы TypeScript

Типы TypeScript используются для определения типов данных переменных, включая встроенные типы, типы, определяемые пользователем, и дополнительные возможности, такие как псевдонимы типов. Основными типами в TypeScript являются string , boolean и number .

В TypeScript псевдоним типа позволяет изменить обозначение типа, не определяя новый тип. Объединённые типы могут быть объявлены только с помощью ключевого слова type . Такой подход предоставляет уникальные возможности, недоступные в других языках, особенно при работе с интерфейсами псевдонимов типов.

С другой стороны, типы кортежей позволяют объявить массив с фиксированным количеством элементов, причём каждый элемент имеет свой собственный тип данных. Интересно, что кортежи могут быть объявлены с помощью типов только в TypeScript.

Интерфейсы TypeScript

Интерфейсы в TypeScript представляют собой контракт, определяющий требования, которым должен удовлетворять объект. Они определяют синтаксис, которого должен придерживаться каждый объект. Компонентами интерфейса в TypeScript являются его свойства, методы и события.

Оптимальные процедуры выбора между типами и интерфейсами в TypeScript включают учёт личных предпочтений и удобочитаемости, оценку контекста и осмысление эффекта производительности.

Ключевые различия между типами и интерфейсами

Хотя типы и интерфейсы имеют ряд общих черт, между ними существуют и ключевые различия, например:

  • работа с примитивными типами
  • объединение и пересечение типов
  • слияния объявлений
  • типы функции и кортежа

Осознание этих различий необходимо для выбора подходящего инструмента для работы и оптимизации разработки на TypeScript.

Примитивные типы

Одним из ключевых различий между типами и интерфейсами является работа с примитивными типами. Типы могут использоваться с примитивными типами, такими как тип string , в то время как интерфейсы не могут использоваться с примитивными типами.

Псевдоним примитивного значения можно получить только с помощью типов, поэтому они являются рекомендуемым выбором для определения примитивных типов.

Объединение и пересечение типов

Ещё одно различие между типами и интерфейсами заключается в том, как они относятся к объединению и пересечению типов. Объединение типов может быть объявлено только с помощью ключевого слова type , а ключевое слово interface в данном контексте не применяется. Такой подход обеспечивает уникальные возможности, недоступные в других языках. Пересечение типов, напротив, создаётся с помощью оператора and в TypeScript.

Хотя интерфейсы могут комбинироваться, образуя объединение типов, они не поддерживают пересечение типов, что делает объединение типов более универсальным и выразительным в подобных ситуациях.

Слияние и расширение объявлений

Слияние объявлений — ключевое различие между интерфейсами и типами. Интерфейсы с одинаковыми именами не должны быть объявлены в одной области видимости. Это приводит к слиянию их объявлений, что может привести к неожиданным ошибкам.

Однако псевдонимы типов объединяют свойства, не приводя к ошибкам. Слияние объявлений может быть полезно для расширения определений типов сторонних библиотек, что делает интерфейсы ценным инструментом в таких сценариях.

Типы функции и кортежа

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

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

Практические случаи использования типов и интерфейсов

Типы и интерфейсы имеют практическое применение в различных аспектах разработки на TypeScript. В зависимости от конкретной ситуации один из них может быть более подходящим, чем другой.

Мы рассмотрим некоторые часто встречающиеся случаи использования, включая объектно-ориентированное программирование, сложные структуры данных, интеграцию сторонних библиотек, и определим, что лучше подходит — типы или интерфейсы.

Объектно-ориентированное программирование

В объектно-ориентированном программировании интерфейсы лучше подходят благодаря их способности расширять классы и поддерживать наследование. Это делает интерфейсы идеальным выбором при работе с парадигмами объектно-ориентированного программирования, поскольку они обеспечивают более структурированный подход и согласованность производных классов.

Комплексные структуры данных

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

Хотя интерфейсы могут использоваться и для более простых структур данных и обеспечивают повышенную удобочитаемость и удобство сопровождения, типы, как правило, являются предпочтительным выбором для более сложных структур данных.

Интеграция со сторонними библиотеками

Интерфейсы полезны для интеграции со сторонними библиотеками благодаря возможности слияния объявлений, о чем говорилось ранее в разделе Слияние и расширение объявлений . Эта возможность позволяет разработчикам настраивать определение типа сторонней библиотеки в соответствии с требованиями конкретного проекта.

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

Учёт производительности и обработки ошибок

Важными аспектами при выборе между типами и интерфейсами являются производительность и обработка ошибок. Хотя влияние использования типов на производительность по сравнению с интерфейсами минимально, интерфейсы обеспечивают более качественные сообщения об ошибках и проверку типов благодаря своей способности обнаруживать конфликты и выбрасывать ошибки при расширении.

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

Проверка типа и сообщения об ошибках

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

В отличие от этого, псевдонимы типов могут не уловить некоторые конфликты, что может привести к неожиданному поведению и затруднить выявление первопричины проблемы.

Влияние на производительность

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

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

Лучшие практики выбора между типами и интерфейсами

Выбор между типами и интерфейсами в конечном итоге зависит от личных предпочтений, удобства чтения и оценки конкретного случая использования. Хотя существуют некоторые общие рекомендации и лучшие практики, окончательное решение должно основываться на уникальных требованиях вашего TypeScript-проекта и предпочтениях вашей команды разработчиков.

Личные предпочтения и удобочитаемость

При выборе типов и интерфейсов разработчики должны учитывать свои личные предпочтения и читаемость кода. Мнения о том, какой из них более читабелен или элегантен, могут быть различными, в зависимости от индивидуальных особенностей и практики кодирования.

Очень важно найти баланс между личными предпочтениями и потребностями проекта, чтобы выбранный подход обеспечивал наилучшую читаемость и удобство сопровождения для всей команды.

Оценка ситуации

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

Рассмотрев эти факторы и оценив плюсы и минусы каждого подхода, разработчики смогут сделать осознанный выбор, наиболее полно отвечающий потребностям их TypeScript-проекта.

Подводя итоги

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

Приступая к изучению TypeScript, помните об этих различиях и выбирайте подходящий инструмент для работы. Воспользуйтесь возможностями системы типов TypeScript и позвольте ей поднять ваш опыт разработки на новую высоту.

Часто задаваемые вопросы

Что лучше использовать в TypeScript — интерфейс или тип

Интерфейсы следует использовать для слияния деклараций и объектно-ориентированного стиля наследования. Типы лучше подходят для работы с функциями и сложными типами.

В чем разница между типом и интерфейсом в TypeScript

Типы в TypeScript более гибкие и могут определять примитивы, пересечения, объединения, кортежи или различные типы данных, а интерфейсы используются для описания формы объекта. Типы используют ключевое слово type для создания нового типа, а интерфейсы — ключевое слово interface для объявления интерфейса.

Когда нужно использовать интерфейс в TypeScript

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

Могут ли типы работать с примитивными типами

Да, типы могут использоваться с примитивными типами, что делает их полезным инструментом для структурирования данных. Однако интерфейсы не могут быть использованы подобным образом.

TypeScript: Об интерфейсах

В этом уроке мы поговорим об интерфейсах. Мы узнаем, что это такое, зачем они нужны, и в каких случаях их стоит использовать вместо типов.

Что такое интерфейс

Интерфейс — это конструкция языка TypeScript, которая используется, чтобы описывать объекты и функции.

Рассмотрим следующий пример:

interface IUser < firstName: string; pointsCount: number; >const user: IUser = < firstName: 'Mark', pointsCount: 100, >; 

В данном фрагменте мы создали интерфейс и реализовали на его основе объект user .

Интерфейс выглядит как определение объектного типа. Объектные типы и интерфейсы взаимозаменяемы почти во всех ситуациях. Сравним с примером выше:

type User = < firstName: string; pointsCount: number; >const user: User = < firstName: 'Mark', pointsCount: 100, >; 

Здесь мы реализовали такой же объект, но уже на основе типа, а не интерфейса. Разницы почти нет.

Документация TypeScript говорит о том, что мы можем выбирать, что использовать — тип или интерфейс. Выбор зависит от ситуации. В таком случае возникает вопрос, зачем нужны интерфейсы, когда уже есть типы?

Когда использовать интерфейсы

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

Главная особенность интерфейсов связана с классами. Классы, которые реализуют интерфейсы, содержат внутри себя свойства и методы, указанные в реализуемом интерфейсе:

interface Countable < count(): number; >class SchoolClass implements Countable < // Тут какая-то логика count(): number < // Обязательно создать этот метод, так как он указан в интерфейсе >> const sc = new SchoolClass(); // Возвращает число студентов в классе sc.count(); 

В этом примере мы реализовали класс на основе интерфейса. Теперь во всех функциях, где объекты используются только для того, чтобы посчитать количество чего-либо внутри них, можно указывать Countable вместо SchoolClass :

// А не function doSomething(obj: SchoolClass) function doSomething(obj: Countable) < // Где-то внутри вызывается obj.count(); >

Так благодаря интерфейсам функция становится более универсальной. Мы можем передать любые объекты, соответствующие Countable , а не только SchoolClass . В программировании такая возможность называется полиморфизмом подтипов (Subtyping).

Задание

Вам дан интерфейс IVehicle. Задача состоит в том, чтобы на основе этого интерфейса реализовать класс Car, который будет иметь метод calcFuelNeeded, принимающий расстояние в километрах и возвращающий расход топлива на указанную дистанцию. Также у класса Car должна быть функция-конструктор, которая принимает и реализует свойства, указанные в интерфейсе.

const porche = new Car(4, 'red', true, 20); console.log(porche.calcFuelNeeded(200)); // 40 

Упражнение не проходит проверку — что делать? ��

Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:

  • Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.

В моей среде код работает, а здесь нет ��

Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.

Мой код отличается от решения учителя ��

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

В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.

Прочитал урок — ничего не понятно ��

Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.

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

Полезное

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *