TypeScript インターフェース インターフェース

導入

インターフェイスはオブジェクトのテンプレートであり、型規約とみなすことができ、中国語では「インターフェイス」と訳されます。特定のテンプレートを使用するオブジェクトには、指定された型構造があります。

インターフェイス 人 {
  名: 文字列;
  姓: 文字列;
  年齢: 番号;
}

上記の例では、インターフェイス person が定義されており、これは 3 つのプロパティ firstNamelastName、および age を持つオブジェクト テンプレートを指定します。このインターフェイスを実装するオブジェクトは、これら 3 つのプロパティを展開し、指定された型に準拠する必要があります。

このインターフェイスの実装は、オブジェクトのタイプとして指定するだけで簡単です。

const p:人 = {
  名前: 'ジョン'、
  姓: 'スミス'、
  年齢:25歳
};

上記の例では、変数 p の型はインターフェイス person であるため、変数 pperson で指定された構造に準拠する必要があります。

角括弧演算子は、インターフェイス プロパティの型を抽出できます。

インターフェース Foo {
  a:文字列。
}

type A = Foo['a']; // 文字列

上の例では、Foo['a'] は属性 a の型を返すため、型 Astring になります。

Interface はオブジェクトのさまざまな構文を表現でき、そのメンバーには 5 つの形式があります。

  • オブジェクトのプロパティ
  • オブジェクトのプロパティインデックス
  • オブジェクトメソッド
  • 関数 -コンストラクタ

(1) オブジェクトのプロパティ

インターフェースポイント{
  x: 数値。
  y:数値;
}

上の例では、「x」と「y」はどちらもオブジェクトのプロパティであり、コロンを使用して各プロパティのタイプを指定します。

属性を区切るにはセミコロンまたはカンマを使用します。最後の属性の末尾にあるセミコロンまたはカンマは省略できます。

属性がオプションの場合は、属性名の後に疑問符を追加します。

インターフェース Foo {
  x?: 文字列;
}

プロパティが読み取り専用の場合は、「readonly」修飾子を追加する必要があります。

インターフェース A {
  読み取り専用 a: 文字列;
}

(2) オブジェクト属性インデックス

インターフェース A {
  [プロパティ: 文字列]: 数値;
}

上の例では、[prop: string] は属性の文字列インデックスです。これは、属性名が文字列である限り、型の要件を満たしていることを意味します。

属性インデックスには stringnumbersymbol の 3 種類があります。

インターフェイスでは、文字列インデックスを 1 つだけ定義できます。文字列インデックスは、名前が文字列である型のすべてのプロパティを制約します。

インターフェース MyObj {
  [プロパティ: 文字列]: 数値;

  a: boolean; // コンパイルエラー
}

上記の例では、属性インデックスは名前が文字列であるすべての属性を指定しており、その属性値は数値 (number) である必要があります。属性「a」の値がブール値の場合、エラーが報告されます。

属性の数値インデックスは、実際には配列のタイプを指定します。

インターフェース A {
  [プロパティ: 数値]: 文字列;
}

const obj:A = ['a', 'b', 'c'];

上記の例では、[prop:number] は属性名の型が数値であることを示しているため、配列を使用して変数 obj に値を割り当てることができます。

同様に、インターフェイスには数値インデックスを 1 つだけ定義できます。数値インデックスは、名前が数値であるすべてのプロパティを制約します。

インターフェイスで文字列インデックスと数値インデックスの両方が定義されている場合、数値インデックスは文字列インデックスに従う必要があります。 JavaScript では、数値プロパティ名は最終的に文字列プロパティ名に自動的に変換されるためです。

インターフェース A {
  [プロパティ: 文字列]: 数値;
  [prop: 数値]: 文字列; // エラーレポート
}

インターフェース B {
  [プロパティ: 文字列]: 数値;
  [prop:数値]: // 正しい;
}

上記の例では、数値インデックスの属性値のタイプが文字列インデックスと一致しない場合、エラーが報告されます。数値インデックスは、文字列インデックスの型宣言と互換性がある必要があります。

(3) オブジェクトメソッド

オブジェクトメソッドを記述するには 3 つの方法があります。

//書き方その1
インターフェース A {
  f(x: ブール値): 文字列;
}

//書き方2
インターフェース B {
  f: (x: ブール値) => 文字列;
}

//書き方3
インターフェース C {
  f: { (x: ブール値): 文字列 };
}

プロパティ名には式を使用できるため、以下のような書き方も可能です。

const f = 'f';

インターフェース A {
  [f](x: ブール値): 文字列;
}

型メソッドはオーバーロードできます。

インターフェース A {
  f(): 数値;
  f(x: ブール値): ブール値;
  f(x: 文字列, y: 文字列): 文字列;
}

インターフェイスでの関数のオーバーロードを実装する必要はありません。ただし、オブジェクト内でメソッドを定義する場合は関数オーバーロード構文を使用できないため、関数メソッドの追加実装をオブジェクトの外部に提供する必要があります。

インターフェース A {
  f(): 数値;
  f(x: ブール値): ブール値;
  f(x: 文字列, y: 文字列): 文字列;
}

関数 MyFunc(): 数値;
関数 MyFunc(x: ブール値): ブール値;
関数 MyFunc(x: 文字列, y: 文字列): 文字列;
関数 MyFunc(
  x?:ブール値|文字列、y?:文字列
):数値|ブール値|文字列 {
  if (x === 未定義 && y === 未定義) は 1 を返します。
  if (typeof x === 'boolean' && y === 未定義) は true を返します。
  if (typeof x === 'string' && typeof y === 'string') return 'hello';
  throw new Error('間違ったパラメータ');
}

const a:A = {
  f:MyFunc
}

上の例では、インターフェース「A」のメソッド「f()」に関数オーバーロードがあります。このオーバーロードを実装するには追加関数「MyFunc()」を定義してから、オブジェクトの属性「f」をデプロイする必要があります。インターフェース Aa は関数 MyFunc() と同じです。

(4) 機能

インターフェイスを使用して、独立した関数を宣言することもできます。

インターフェイスの追加 {
  (x:数値、y:数値): 数値;
}

const myAdd:Add = (x,y) => x + y;

上記の例では、インターフェース Add が関数の型を宣言しています。

(5) コンストラクタ

new キーワードをインターフェイス内で使用して、コンストラクターを表すことができます。

インターフェース ErrorConstructor {
  新しい (メッセージ?: 文字列): エラー;
}

上記の例では、インターフェイス ErrorConstructor 内に new コマンドがあり、それがコンストラクターであることを示しています。

TypeScript では、コンストラクターは特に constructor 属性を持つクラスを指します。詳細については、「クラス」の章を参照してください。

インターフェースの継承

インターフェイスは、主に次の状況で他の型を継承できます。

インターフェースはインターフェースを継承します

インターフェースは「extends」キーワードを使用して他のインターフェースを継承できます。

インターフェースシェイプ {
  名前: 文字列;
}

インターフェース Circle extend Shape {
  半径: 数値;
}

上記の例では、CircleShape を継承しているため、Circle には実際には 2 つの属性 nameradius があります。このとき、「Circle」がサブインターフェース、「Shape」が親インターフェースになります。

extends キーワードは継承されたインターフェイスからプロパティ タイプをコピーするため、重複したプロパティを記述する必要はありません。

インターフェイスでは多重継承が可能です。

インターフェーススタイル {
  色: 文字列;
}

インターフェースシェイプ {
  名前: 文字列;
}

インターフェイス Circle はスタイル、形状を拡張します {
  半径: 数値;
}

上記の例では、CircleStyleShape の両方を継承しているため、colornameradius の 3 つの属性を持ちます。

複数のインターフェイスの継承は、実際には複数の親インターフェイスのマージと同じです。

サブインターフェイスと親インターフェイスに同じ名前の属性がある場合、サブインターフェイスの属性が親インターフェイスの属性をオーバーライドします。サブインターフェイスと親インターフェイスの同じ名前を持つプロパティは型互換である必要があり、競合してはいけないことに注意してください。競合しない場合はエラーが報告されます。

インターフェース Foo {
  ID: 文字列;
}

インターフェイス Bar は Foo を拡張します {
  id: 番号; // エラーレポート
}

上記の例では、「Bar」は「Foo」を継承していますが、両者の同じ名前のプロパティ「id」の型に互換性がないため、エラーが発生します。

多重継承中、複数の親インターフェイスに同じ名前の属性がある場合、同じ名前のこれらの属性に型の競合があってはなりません。競合しないと、エラーが報告されます。

インターフェース Foo {
  ID: 文字列;
}

インターフェースバー{
  ID: 番号;
}

// エラーを報告する
インターフェース Baz は Foo、Bar を拡張します {
  タイプ: 文字列;
}

上記の例では、BazFooBar の両方を継承していますが、後者 2 つの同じ名前の属性 id に型の競合があり、エラーが発生します。

インターフェースの継承タイプ

インターフェイスは、type コマンドで定義されたオブジェクト タイプを継承できます。

タイプ 国 = {
  名前: 文字列;
  大文字: 文字列;
}

インターフェース CountryWithPop extend Country {
  人口: 数;
}

上記の例では、 CountryWithPop は、type コマンドによって定義された country オブジェクトを継承し、population 属性を追加します。

type コマンドで定義した型がオブジェクトでない場合、インターフェースを継承できないことに注意してください。

インターフェース継承クラス

インターフェイスはクラスから継承することもできます。つまり、クラスのすべてのメンバーを継承します。クラスの詳細な説明については、次の章を参照してください。

クラスA {
  x:文字列 = '';

  y():ブール値 {
    true を返します。
  }
}

インターフェイス B は A を拡張します {
  z: 数値
}

上の例では、BA を継承しているため、B は属性 xy()、および z を持ちます。

B インターフェイスを実装するオブジェクトは、これらのプロパティを実装する必要があります。

const b:B = {
  ×: ''、
  y: function(){ true を返す }、
  z:123
}

上記の例では、オブジェクト 'b' はインターフェイス 'B' を実装し、インターフェイス 'B' はクラス 'A' を継承します。

一部のクラスにはプライベート メンバーがあり、インターフェイスはそのようなクラスを継承できますが、それはほとんど重要ではありません。

クラスA {
  プライベート x: 文字列 = '';
  保護された y: 文字列 = '';
}

インターフェイス B は A を拡張します {
  z: 数値
}

// エラーを報告する
const b:B = { /* ... */ }

// エラーを報告する
クラス C は B を実装します {
  // ...
}

上記の例では、「A」にはプライベート メンバーと保護されたメンバーがあり、「B」は「A」を継承しますが、オブジェクトはこれらのメンバーを実装できないため、オブジェクトに使用できません。これにより、B は他のクラスでのみ使用されるようになり、このとき、他のクラスと A の間には親クラスと子クラスの関係が成立しないため、xy をデプロイできなくなります。 。

インターフェースのマージ

同じ名前の複数のインターフェイスは 1 つのインターフェイスにマージされます。

インターフェースボックス {
  高さ: 数値;
  幅: 数値;
}

インターフェースボックス {
  長さ: 数値;
}

上の例では、2 つの Box インターフェースが、heightwidthlength の 3 つのプロパティを持つ 1 つのインターフェースにマージされます。

この設計は主に JavaScript の動作との互換性を目的としています。 JavaScript 開発者は、独自のプロパティやメソッドをグローバル オブジェクトや外部ライブラリに追加することがよくあります。その後、インターフェイスを使用してこれらのカスタム プロパティとメソッドの型を指定する限り、それらは元のインターフェイスと自動的にマージできるため、外部型を拡張するのが非常に便利になります。

たとえば、Web ページ開発ではカスタム プロパティを「window」オブジェクトや「document」オブジェクトに追加することがよくありますが、元の定義にはこれらのプロパティが含まれていないため、TypeScript はエラーを報告します。解決策は、カスタム属性をインターフェイスとして記述し、それを元の定義にマージすることです。

インターフェースドキュメント {
  foo: 文字列;
}

document.foo = 'こんにちは';

上記の例では、インターフェース Document によってカスタム プロパティ foo が追加され、そのカスタム プロパティを document オブジェクトで使用できるようになります。

同じ名前のインターフェイスをマージする場合、同じ属性に対して複数の型宣言がある場合、相互に型の競合があってはなりません。

インターフェース A {
  a:数字。
}

インターフェース A {
  a: 文字列; // エラーレポート
}

上記の例では、インターフェイス A の属性 a に 2 つの型宣言があり、互いに競合するため、エラーが発生します。

同じ名前のインターフェイスをマージする場合、同じ名前のメソッドの型宣言が異なる場合、関数のオーバーロードが発生します。また、後の定義の方が前の定義よりも優先されます。

インターフェイスクローナー {
  クローン(動物: 動物): 動物。
}

インターフェイスクローナー {
  クローン(動物: 羊): 羊;
}

インターフェイスクローナー {
  クローン(動物: 犬): 犬;
  クローン(動物: 猫): 猫;
}

// と同等
インターフェイスクローナー {
  クローン(動物: 犬): 犬;
  クローン(動物: 猫): 猫;
  クローン(動物: 羊): 羊;
  クローン(動物: 動物): 動物。
}

上記の例では、clone() メソッドに異なる型宣言があり、関数のオーバーロードが発生します。このとき、後から定義したものほど優先順位が高く、関数のオーバーロードが早いものとなります。たとえば、clone(animal: Animal) は最初の型宣言であり、関数のオーバーロードで最後にランクされ、clone() 関数によって一致する最後の型に属します。

この規則には例外が 1 つあります。同名のメソッドのうち、1 つのパラメータがリテラル型の場合、リテラル型が優先されます。

インターフェース A {
  f(x:'foo'): ブール値;
}

インターフェース A {
  f(x:any): 無効;
}

// と同等
インターフェース A {
  f(x:'foo'): ブール値;
  f(x:any): 無効;
}

上記の例では、f() メソッドにはリテラル型である型宣言パラメータ 'x' があり、この型宣言は最も高い優先順位を持ち、関数のオーバーロードで最初にランクされます。

実際の例は、Document オブジェクトの createElement() メソッドです。これは、さまざまなパラメーターに基づいてさまざまな HTML ノード オブジェクトを生成します。

インターフェースドキュメント {
  createElement(タグ名: 任意): 要素;
}
インターフェースドキュメント {
  createElement(タグ名: "div"): HTMLDivElement;
  createElement(タグ名: "スパン"): HTMLSpanElement;
}
インターフェースドキュメント {
  createElement(タグ名:文字列): HTML要素;
  createElement(タグ名: "canvas"): HTMLCanvasElement;
}

// と同等
インターフェースドキュメント {
  createElement(タグ名: "canvas"): HTMLCanvasElement;
  createElement(タグ名: "div"): HTMLDivElement;
  createElement(タグ名: "スパン"): HTMLSpanElement;
  createElement(タグ名: string): HTMLElement;
  createElement(タグ名: 任意): 要素;
}

上記の例では、createElement() メソッドの関数オーバーロード、リテラル パラメーターを含む型宣言が最初にランク付けされ、特定の HTML ノード オブジェクトが返されます。あまり具体的でないタイプのパラメータは後でランク付けされ、汎用の HTML ノード オブジェクトが返されます。

2 つのインターフェイスで構成される共用体型に同じ名前のプロパティが存在する場合、そのプロパティの型も共用体型になります。

インターフェースサークル{
  エリア: bigint;
}

インターフェース Rectangle {
  エリア: 数値;
}

const を宣言します: 円 |

s.area; // bigint |

上記の例では、インターフェイス CircleRectangle は共用体型 Circle | Rectangle を形成します。したがって、この共用体型と同じ名前の属性 area' も共用体型です。この例の declareコマンドは、他のスクリプト ファイルによって与えられる変数s` の特定の定義を表します。詳細については、「declare コマンド」の章を参照してください。

インターフェースとタイプの類似点と相違点

「interface」コマンドは「type」コマンドに似ており、どちらもオブジェクトのタイプを表すことができます。

多くのオブジェクト タイプは、インターフェイスまたはタイプのいずれかで表すことができます。さらに、この 2 つは多くの場合、同じ意味で使用でき、ほとんどすべてのインターフェイス コマンドは type コマンドとして書き換えることができます。

それらの類似点は、まず両方ともオブジェクト タイプに名前を付けることができるという事実に反映されます。

タイプ 国 = {
  名前: 文字列;
  大文字: 文字列;
}

インターフェース 国 {
  名前: 文字列;
  大文字: 文字列;
}

上記の例は、同じ型を定義する type コマンドと interface コマンドです。

class コマンドも、クラスの定義とオブジェクト タイプの定義を同時に行うことにより、同様の効果をもたらします。ただし、コンパイル後も保持される値が作成されます。型だけが必要な場合は、type または interface を使用する必要があります。

インターフェースとタイプの違いは以下の通りです。

(1) type は非オブジェクト型を表すことができますが、interface はオブジェクト型 (配列、関数などを含む) のみを表すことができます。

(2) interface は他の型を継承できますが、type は継承をサポートしていません。

継承の主な機能は、type で定義されたオブジェクト型に属性を追加することです。型を再定義するには、& 演算子のみを使用できます。

動物の種類 = {
  名前: 文字列
}

タイプ クマ = 動物 & {
  ハニー: ブール値
}

上記の例では、タイプ「Bear」は、「Animal」に基づいて属性「honey」を追加します。

上記の例の「&」演算子は、2 つの型の特性を同時に持つことを意味し、2 つのオブジェクト型をマージする役割を果たすことができます。

比較のために、interface は継承を使用して属性を追加します。

インターフェース 動物 {
  名前: 文字列
}

インターフェース Bear が動物を拡張 {
  ハニー: ブール値
}

継承する場合、型とインターフェイスは同じ意味で使用できます。インターフェイスは型を継承できます。

タイプ Foo = { x: 数値 };

インターフェイス Bar は Foo を拡張します {
  y: 数値。
}

type はインターフェイスを継承することもできます。

インターフェース Foo {
  x: 数値。
}

タイプ Bar = Foo & { y: 数値 };

(3) 同じ名前の interface は自動的にマージされ、同じ名前の type はエラーを報告します。つまり、TypeScript では、「type」を使用して同じ型を複数回定義することはできません。

type A = { foo:number }; // エラーレポート
type A = { bar:number }; // エラーレポート

上の例では、type がタイプ A を 2 回定義しているため、両方の行でエラーが報告されます。

比較のため、「interface」は自動的にマージされます。

インターフェース A { foo:number };
インターフェイス A { バー: 番号 };

const obj:A = {
  フー: 1、
  バー: 1
};

上の例では、interface はタイプ A の 2 つの定義をマージします。

これは、インターフェイスがオープンで属性を追加できるが、型がクローズで属性を追加できず、新しい型のみを定義できることを示しています。

(4) interface には属性マッピング (マッピング) を含めることはできませんが、type には含めることができます。詳細については、「マッピング」の章を参照してください。

インターフェースポイント{
  x: 数値。
  y: 数値。
}

// 正しい
タイプ PointCopy1 = {
  [ポイントのキーのキー]: ポイント[キー];
};

// エラーを報告する
インターフェイス PointCopy2 {
  [ポイントのキーのキー]: ポイント[キー];
};

(5) this キーワードは interface でのみ使用できます。

// 正しい
インターフェース Foo {
  add(num:number): これ;
};

// エラーを報告する
タイプ Foo = {
  add(num:number): これ;
};

上記の例では、type コマンドによって宣言されたメソッド add()this を返すとエラーが返されます。インターフェースコマンドにはこの問題はありません。

以下は this の実際のオブジェクトを返す例です。

クラス電卓は Foo を実装します {
  結果 = 0;
  add(数値:数値) {
    this.result += num;
    これを返します。
  }
}

(6) type はプリミティブ データ型を拡張できますが、インターフェイスは拡張できません。

// 正しい
type MyStr = 文字列 & {
  タイプ: '新しい'
};

// エラーを報告する
インターフェイス MyStr は文字列 { を拡張します
  タイプ: '新しい'
}

上の例では、type は元のデータ型文字列を拡張できますが、interface は拡張できません。

(7) interface は一部の複合型 (交差型や共用体型など) を表現できませんが、type は表現できます。

type A = { /* ... */ };
type B = { /* ... */ };

タイプ AorB = A |
type AorBwithName = AorB & {
  名前: 文字列
};

上記の例では、型 AorB は共用体型であり、AorBwithName によってプロパティが AorB に追加されます。これらの操作はどちらも「インターフェース」では表現できません。

要約すると、複雑な型の操作がある場合は、一般に type を使用する以外に選択肢はありません。interface の方が柔軟性があり、型の拡張や自動マージが簡単なので、最初に使用することをお勧めします。 。


作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0