web-dev-qa-db-ja.com

TypeScriptのインターフェイスの拡張と交差の違いは?

次のタイプが定義されているとしましょう:

interface Shape {
  color: string;
}

ここで、このタイプにプロパティを追加する次の方法を検討してください。

拡張子

interface Square extends Shape {
  sideLength: number;
}

交差点

type Square = Shape & {
  sideLength: number;
}

両方のアプローチの違いは何ですか?

そして、完全性と好奇心のために、同等の結果をもたらす他の方法はありますか?

13
Willem-Aart

はい、あなたのシナリオに関係があるかもしれないし、そうでないかもしれない違いがあります。

おそらく最も重要なのは、同じプロパティキーを持つメンバーが両方のタイプに存在する場合の処理​​方法の違いです。

考慮してください:

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宣言はmergedであるため、新しいメンバーはどこにでも追加できます。

動作をマージする一般的な使用方法は次のとおりです

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ハンドブックで両方の構成について詳しく読むことができます。 Interfaces および Advanced Types セクションは特に関連があります。

15
Aluan Haddad