Есть ли семантическая разница между интерфейсами и псевдонимами типов?

Почти то же самое я могу сделать с интерфейсами и псевдонимами типов.

Например

Классы могут реализовывать либо интерфейсы, либо псевдонимы типов.

interface Shape {
    area(): number;
}

type Perimeter = {
    perimeter(): number;
}

class Rectangle implements Shape, Perimeter {
}

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

interface A {
    a: string;
}

type B = {
    b: string
}

interface C extends B {
    c: string;
}

type D = A & {
    d: string;
}

Есть ли семантическая разница между интерфейсами и аннотациями типов?

Возможный дубликат Typescript: интерфейсы против типов

Karol Majewski 05.02.2019 15:42

@KarolMajewski Да, и, к сожалению, лучший ответ не является принятым ответом.

Paleo 05.02.2019 15:54

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

user1283776 05.02.2019 15:57
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
6
3
792
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

type A = {
  a: string;
}

type A = { // <-- error: duplicate identifier A
  b: string;
}

interface A {
  a: string;
}

interface A { // <-- okay, A is { a: string, b: string } 
  b: string; 
}

edit: изменено расширение на *implement

редактировать 2: пересечения не совпадают с расширением. Рассмотрим следующее:

interface A {
  num: number;
  str: string; 
}

type B = A & { // <-- this is okay, but overwrites the num property type to number & string
  arr: any[];
  num: string;
}

interface C extends A { // <-- error: C incorrectly extends A
  num: string;
}

редактировать 3: еще одно потенциально существенное отличие для некоторых людей (хотя и не обязательно семантическое различие) заключается в том, что типы перечислены в подсказках (по крайней мере, в vscode), а интерфейсы - нет.

type также может расширять class: type MyType = MyClass & { otherProp: string; }. Кажется, слияние объявлений - единственная разница?

Paleo 05.02.2019 15:44

Еще одно отличие: type может быть составлен из | и может быть псевдонимом примитивных типов.

Paleo 05.02.2019 15:48

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

user1283776 05.02.2019 15:55

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

Robbie Milejczak 05.02.2019 16:06

@ user1283776 да, различия незначительны, но самая большая семантическая разница заключается в том, что интерфейсы могут реализовывать классы, расширять другие интерфейсы и сливаться друг с другом. Существует правило tslint под названием interface-over-type-literal, и люди из palantir объясняют это так: «Интерфейсы обычно предпочтительнее литералов типов, потому что интерфейсы могут быть реализованы, расширены и объединены».

Robbie Milejczak 05.02.2019 16:08

@user1283776 user1283776 Когда интерфейс расширяет другой интерфейс, реальной семантической связи родитель/потомок не существует. Это так же, как с первым интерфейсом & новых членов.

Paleo 05.02.2019 16:13

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

Robbie Milejczak 05.02.2019 16:14

@RobbieMilejczak Но вы можете сделать: interface C extends A { num: string & number; }, и вы получите точно такой же результат, как и при пересечении типов. (EDIT: нет, пересечение типов не "перезаписывает тип свойства num". Это все еще пересечение.)

Paleo 05.02.2019 16:16

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

Robbie Milejczak 05.02.2019 16:19

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

Robbie Milejczak 05.02.2019 16:25

@RobbieMilejczak: Можете ли вы объяснить семантическую разницу между интерфейсами и типами не только технически? Как будто кто-то может объяснить разницу между классами и объектами, поскольку классы являются шаблонами для объектов, а объекты являются экземплярами классов. Я не понимаю последствий «самой большой семантической разницы в том, что интерфейсы могут реализовывать классы, расширять другие интерфейсы и сливаться друг с другом» для 90+% моих вариантов использования. Я все еще ищу любые возможные семантические указания о том, следует ли использовать интерфейсы или псевдонимы типов в этих случаях.

user1283776 06.02.2019 10:29

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

Robbie Milejczak 06.02.2019 14:47
Ответ принят как подходящий

Существуют технические различия между interface и type: здесь хорошо описано.

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

О наследовании interface и пересечении type в TypeScript

В TypeScript иерархия между интерфейсами — это просто способ определения интерфейсов. Но как только они определены, между интерфейсами не возникает реальных отношений родитель-потомок. Например:

interface Named {
    name: string
}
interface Person extends Named {
    age: number
}
interface Animal {
    name: string
    age: number
}

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

function useNamed(named: Named) {
}
let p: Person = /* ... */
let a: Animal = /* ... */
useNamed(p) // OK
useNamed(a) // also OK, because if 'Animal' is compatible with
            // `Named` then it is a `Named`

Вот почему тот же тип можно создать и с помощью тип перекрестка:

type Engine = Named & {
    age: number
}

Из спецификации:

Intersection types represent values that simultaneously have multiple types. A value of an intersection type A & B is a value that is both of type A and type B. (source: TypeScript Specification)

Наш тип Engine — это и Named, и дополнительное определение: семантически это то же самое, что и наследование интерфейса. И тип Engine здесь точно такой же, как Person и Animal.

Спасибо! Если я могу задать сопутствующие дополнительные вопросы: как вы думаете, нужны ли интерфейсы и типы в TypeScript, или вы думаете, что только один из них будет включен, если TypeScript будет переработан с нуля?

user1283776 06.02.2019 08:44

@ user1283776 Я не уверен. Но IMO текущий синтаксис в порядке. Есть несколько способов сделать одно и то же, это похоже на JavaScript. Мы можем сказать, что интерфейсы — это «типы в фигурных скобках»: тогда синтаксис type someType = { … } — это анонимный интерфейс, присвоенный типу, точно так же, как функция — именованная константа, но const myFn = function () { … } — это анонимная функция, назначенная константе.

Paleo 06.02.2019 09:28

Другие вопросы по теме