Разница между расширяющимися и пересекающимися интерфейсами в TypeScript? - PullRequest
0 голосов
/ 06 октября 2018

Допустим, определен следующий тип:

interface Shape {
  color: string;
}

Теперь рассмотрим следующие способы добавления дополнительных свойств к этому типу:

Расширение

interface Square extends Shape {
  sideLength: number;
}

Пересечение

type Square = Shape & {
  sideLength: number;
}

В чем разница между обоими подходами?

И, ради полноты и из любопытства, есть ли другие способы для получения сопоставимых результатов?

1 Ответ

0 голосов
/ 06 октября 2018

Да, есть различия, которые могут или не могут иметь значение в вашем сценарии.

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

Учтите:

interface NumberToStringConverter {
  convert: (value: number) => string;
}

interface BidirectionalStringNumberConverter extends NumberToStringConverter {
  convert: (value: string) => number;
}

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

error TS2430: Interface 'BidirectionalStringNumberConverter' incorrectly extends interface 'NumberToStringConverter'.

  Types of property 'convert' are incompatible.
      Type '(value: string) => number' is not assignable to type '(value: number) => string'.
          Types of parameters 'value' and 'value' are incompatible.
              Type 'number' is not assignable to type 'string'.

Однако, если мы используем типы пересечений

interface NumberToStringConverter = {
    convert: (value: number) => string;
}

type BidirectionalStringNumberConverter = NumberToStringConverter & {
    convert: (value: string) => number;
}

Нет ошибки вообще и далее, учитывая

declare const converter: BidirectionalStringNumberConverter;

converter.convert(0); // `convert`'s call signature comes from `NumberToStringConverter`

converter.convert('a'); // `convert`'s call signature comes from `BidirectionalStringNumberConverter`

// And this is a good thing indeed as a value conforming to the type is easily conceived

const converter: BidirectionalStringNumberConverter = {
  convert: (value: string | number) =>
    typeof value === 'string'
      ? Number(value)
      : String(value)
}

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

Здесь обычно используется поведение слияния

lib.d.ts

interface Array<T> {
    // map, filter, etc.
}

array-flat-map-polyfill.ts

interface Array<T> {
    flatMap<R>(f: (x: T) => R[]): R[];
}

if (typeof Array.prototype.flatMap !== 'function') {
    Array.prototype.flatMap = function (f) {
        return this.map(f).reduce((xs, ys) => [...xs, ...ys], []);
    }
}

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

Напротив, типы пересечений, хранящиеся в объявлении type, закрыты и не подлежат объединению.

Есть много, много различий.Вы можете прочитать больше об обеих конструкциях в TypeScript Handbook.Разделы Interfaces и Advanced Types особенно актуальны.

...