interfaceとは、オブジェクトの形状のことです。 標準的なJavaScriptのオブジェクトはkey:valueペアのマップである。 JavaScriptオブジェクトのキーはほとんどの場合文字列で、その値はサポートされているJavaScriptの値(primitiveやabstract)です。

インターフェースはTypeScriptコンパイラに、オブジェクトが持つことのできるプロパティ名とそれに対応する値の型を伝えるものです。

プロパティ(キー)と値を持つオブジェクトを定義すると、TypeScriptはオブジェクトのプロパティ名とその値のデータ型を見て、暗黙のうちにインターフェースを作成する。 これは型推論のために起こる。

(object-shape.ts)

上記の例では、firstNamelastNameagegetSalaryフィールドを持つオブジェクト student を作り、いくつかの初期値を代入している。

{
firstName: string;
lastName: string;
age: number;
getSalary: (base: number) => number;
}

インターフェースはオブジェクトと同様であるが、オブジェクトのプロパティとその型に関する情報のみを含む。 また、インターフェース型を作成して名前を付けると、それを使ってオブジェクトの値に注釈を付けることができますが、ここでは暗黙的に作成されているので、このインターフェースは名前を持っていません。 前回のレッスンで、最初は暗黙的に作成され、その後、型エイリアスを使用して明示的に関数型を作成した関数型と比較できます。

定義した後に、オブジェクトのプロパティをいじってみてください。

(shape-override.ts)

上の例からわかるように、rossの型が暗黙のインターフェースなのでTypeScriptはオブジェクトの形を記憶しているのだそうです。 もし、プロパティの値をインターフェイスで指定されている以外の異なる型の値で上書きしようとしたり、インターフェイスで指定されていない新しいプロパティを追加しようとすると、TypeScriptコンパイラーはプログラムをコンパイルしてくれません。

オブジェクトに基本的にどんなプロパティでも持たせたい場合は、明示的に値anyをマークすれば、割り当てられたオブジェクト値から型を推測してくれません。

(shape-override-any.ts)

Interface Declaration

The implicit interface we have been technically a type, but it wasn’t defined explicitly.TWITCH

(argument-with-shape.)

(argument-with-shape.ts)

上記の例では、firstName, lastName, age, getSalary フィールドを持つオブジェクト引数を受け取る関数 getPersonInfo を定義しています。 :<type>アノテーションを使用して、プロパティ名とそれに対応する型を含むオブジェクトを型として使用していることに注意してください。 これは、インターフェイスに名前がなく、インラインで使用されているため、匿名インターフェイスの例となります。 ross オブジェクトがさらに複雑になり、複数の場所で使用されるようになると、TypeScriptは最初は気に入っていたものの、今では扱うのが大変なものに思えてしまうのです。

(argument-with-interface.ts)

上の例で、オブジェクトの形を表すインタフェース Person を定義しましたが、今回はこの型を参照できる名前を持っているのです。 この型を使用して、ross変数とgetPersonIfo関数のperson引数にアノテーションを付けています。

なぜインターフェースを使うのか?

インターフェース型は、特定の形状を強制するのに重要な場合がある。 通常 JavaScript では、オブジェクトが常に特定のプロパティを含み、そのプロパティが常に {age: 21, ...} のような特定の型の値を持つことを実行時に盲信します。

そのプロパティがオブジェクトに存在するか、その値が期待通りであるかを最初にチェックせずに、実際にそのプロパティに対する操作を開始すると、うまくいかないことがあり、後でアプリケーションが使用できなくなることがあります。 例えば、{age: '21', ...}、ここではageの値がstring.

インターフェイスは、このようなシナリオをコンパイル時に安全に処理するメカニズムを提供します。 誤って存在しないオブジェクトのプロパティを使用したり、プロパティの値を不正な操作で使用したりすると、TypeScriptコンパイラはプログラムをコンパイルしてくれません。

(interface-safety.ts)

上記の例で、printStudent関数内で_student引数のnameプロパティを使ってみようとしています。 _student引数はStudentインターフェースの型であるため、このプロパティはStudentインターフェースに存在しないため、TypeScriptコンパイラーはコンパイル時にエラーを投げる。

同様に、100 — _student.firstNamefirstNameプロパティがstringの型であり、前回調べたところ、JavaScriptではnumberからstringを引くことはできない(結果はNaN)ので有効な操作ではない。

上記の例では、getSalaryフィールドに対して従来の方法で関数型を記述している。 しかし、同じように body のない関数構文を使用することもでき、これは一般的にインターフェースで使用されます。

interface Student {
firstName: string;
lastName: string;
age: number;
getSalary(base: number): number;
};

Optional Properties

時には、オブジェクトに特定のデータ型のデータを保持するプロパティが必要ですが、そのプロパティがオブジェクトにあることが必須でない場合があります。 これは、前のレッスンで学んだオプションの関数パラメータに似ています。

このようなプロパティは、オプショナルプロパティと呼ばれます。 インターフェースはオプションのプロパティを含むことができ、オプションの関数パラメータと同様に、?:Typeアノテーションを使って表現します。

(optional-properties.ts)

上記の例では、Studentインターフェースにはageプロパティという、オプションであるプロパティが用意されています。 しかし、ageプロパティが提供される場合、number型の値を持たなければならない。

Studentインターフェースの型であるrossオブジェクトの場合、ageプロパティに値を提供していないので合法であるが、monicaの場合、ageプロパティは提供しているが値はstringで合法でない。

このエラーは奇妙に見えるかもしれませんが、実は意味があるのです。 もしオブジェクトにageプロパティが存在しなければ、object.ageundefinedという型を返します。

したがって、age プロパティの値は undefined 型か number 型のいずれかであり、TypeScript ではユニオン構文 number | undefined で表される。

💡 タイプシステムのレッスンでユニオンを学ぶ。 例えば、算術演算で age プロパティを使用し、その値が undefined であったとします。 これは一種の深刻な問題です。

しかし、良いことに、TypeScript コンパイラーは、値が undefined である可能性があるので、オプショナルプロパティに対して不正な操作を行うことを許しません。ts)

上記の例では、ageプロパティに対して算術演算を行っていますが、このプロパティの値は実行時にnumberまたはundefinedになりうるため、不正な演算となっています。 しかし、上記のプログラムでは、TypeScriptのコンパイラーフラグである--strictNullChecksフラグをfalseに設定する必要がありました。

このエラーや警告を回避するには、このプロパティがnumberの型であり、numberundefinedではないことを明示的にTypeScriptコンパイラに伝える必要があります。

(optional-properties-safety-override.ts)

上記のプログラムでは、_student.ageの型をnumber | undefinedからnumberに変換している(_student.age as number)を使っていますが、は型がnumberに変換されていることを表しています。 これは、TypeScriptのコンパイラに「おい、これは数字だぞ」と伝えるための方法である。

💡 型アサーションについては、タイプシステムのレッスンで学びます。

インターフェースを使った関数型

オブジェクトの形だけでなく、インターフェースは関数のシグネチャも表すことができる。 前回のレッスンでは、関数型を記述するためにタイプエイリアスを使用しましたが、インターフェースでもそれが可能です。

interface InterfaceName {
(param: Type): Type;
}

インターフェースを関数型として宣言する構文は、関数のシグネチャそのものと似ています。 上の例からわかるように、インターフェースのボディは、もちろんボディを除いた匿名関数のシグネチャを正確に含んでいます。 ここではパラメータ名は重要ではありません。

(function-signature.ts)

上の例で、number 型の 2 つの引数を受け取り、boolean 値を返す関数型を定義している IsSumOdd インターフェイスが定義されています。 IsSumOdd インターフェース型は関数型 (x: number, y: number) => boolean と等しいので、この型を使って関数を記述することができます。

匿名メソッドシグネチャを持つインターフェースは、関数を記述するものです。 しかし、JavaScriptの領域での関数はオブジェクトでもあり、オブジェクトと同じように関数の値にプロパティを追加することができることを意味します。

(function-signature-with-methods.ts)

上の例で、関数を表す IsSumOdd インターフェイスに対して typecalculate プロパティを追加したところ、

となりました。 Object.assignメソッドを使用して、typecalculateのプロパティを関数値にマージしています。

関数型のインターフェースは、コンストラクタ関数を記述するのに役立ちます。 コンストラクタ関数は、オブジェクト(インスタンス)を作成することを仕事とするクラスに似ています。 ES5まではJavaScriptのclassを模倣したコンストラクタ関数しかなかったのです。

💡 コンストラクタ関数について詳しく知りたい方は、こちらの記事を参考にしてください。 つまり、この関数は、通常の関数呼び出しではなく、オブジェクトを生成するために new キーワードを使用してのみ呼び出すことができます。 幸いなことに、コンストラクタ関数を使用する必要はありません。TypeScriptにはclassキーワードがあり、コンストラクタ関数よりもはるかに簡単にクラスを作成できます。 実際、classはJavaScriptではコンストラクタ関数にあたります。

class Animal{
constructor( _name ) {
this.name = _name;
}
}console.log( typeof Animal ); // "function"

クラスとコンストラクタ関数は同じものです。 唯一の違いは、class が豊富な OOP 構文を与えてくれることです。 したがって、コンストラクタ関数型のインターフェースはクラスを表します。

(function-signature-with-new.ts)

上の例で、型 string の引数を受け取るコンストラクタ関数でクラスを定義しています。

AnimalInterfaceは、newキーワードの前にanonymous functionがあるので、これをAnimalのコンストラクタと同様のシグネチャを持つコンストラクタ関数と考えることができます。 これは、AnimalクラスがAnimalInterfaceの型として適格であることを意味する。 ここで、AnimalInterfaceインターフェース型はnew (sound: string) => any関数型に相当します。

createAnimal関数はAnimalInterface型のctor引数を受け取るので、引数としてAnimalクラスを渡すことができます。 その理由はクラスのレッスンで説明します。

インデックス型

インデックス型オブジェクトとは、objのようなインデックス署名を使ってプロパティにアクセスできるオブジェクトのことを指します。 これは配列要素にアクセスするデフォルトの方法ですが、オブジェクトに対しても行うことができます。

var a = ;
var o = { one: 1, two: 2, three: 3 };console.log( a ); // 1
console.log( a ); // 1 (same as `a.one`)

時には、オブジェクトが明確な形状を持たずに任意の数のプロパティを持つことがあります。 そのような場合は、object型を使えばよいのです。 しかし、このobject型は、numberstringbooleansymbolnullundefinedではない任意の値を定義します。

もし、値がプレーンな JavaScript オブジェクトであるかを厳密にチェックする必要があるなら、問題が生じるかも知れません。

interface SimpleObject {
: any;
}

SimpleObjectインターフェースは、値がanyデータ型であるstringキーを持つオブジェクトの形状を定義しています。

(indexable-type-object.ts)

上の例では、SimpleObjectインターフェースのタイプでrossmonicaオブジェクトを定義しています。 これらのオブジェクトはstringキーとanyデータ型の値を含んでいるので、完全に合法です。

もしあなたがmonicaのキー1number型であることについて混乱しているなら、これは合法です。JavaScriptにおけるオブジェクトまたは配列アイテムは、以下のようにnumberまたはstringキーを使用してインデックスされることができるので、

var o = { 0: 'Zero', '1': 'One' };
var a = ;console.log( o ); // Zero
console.log( o ); // One
console.log( a ); // One
console.log( a ); // One

キーとその値の型についてもっと正確にする必要があるなら、確かにそれも可能でしょう。 例えば、私たちが望むなら、number型のキーとnumber型の値を持つインデックス署名可能なインターフェース型を定義することができます。

💡 インデックス署名キー型はstringnumberのどちらかでなければなりません。

(indexable-type-number.ts)

上記の例で、number型のプロパティ名とnumber型の値を含むことができる、LapTimesインターフェースを定義しています。 このインターフェイスは number のキーを使ってインデックスを作成できるデータ構造を表すことができるので、配列 ross やオブジェクト monica および joey は合法です。

しかし、キー onestring なので rachel オブジェクトは LapTimes に準拠せず、それ以外の string でアクセスすることができないため、rachel のように何もできない。

インデックス可能なインターフェース型では、いくつかのプロパティを必須、いくつかのプロパティを任意とすることが可能です。 これは、オブジェクトが特定の形状を持つ必要がある場合に非常に便利ですが、オブジェクトに余分なプロパティや不要なプロパティが含まれていても、実際には問題ではありません。ts)

上記の例では、string値を持つプロパティnamenumber値を持つオプションのプロパティageを含まなければならないインタフェースLapTimesを定義しています。 LapTimes 型のオブジェクトは、キーが number で値も number でなければならない任意のプロパティを持つこともできます。

オブジェクト ross は、オプションであるため age プロパティを持っていなくても LapTimes オブジェクトとして有効です。

オブジェクトjoeystringの型であるgenderプロパティを持っているので、LapTimesインタフェースに適合していません。 rachel オブジェクトは LapTimes インターフェースで要求される name プロパティを持っていません。

💡 インデクサブルタイプを使うときに注意しなければならないことがいくつかあります。

Extending Interface

クラスと同様に、インターフェースは他のインターフェースからプロパティを継承することができます。 しかし、JavaScriptのクラスとは異なり、インターフェイスは複数のインターフェイスから継承することができます。

(extend-interface.ts)

インターフェイスを継承することにより、子インターフェースは親インターフェースが持つ全てのプロパティを取得することができます。 これは、複数のインターフェイスが共通の構造を持ち、共通のプロパティを共通のインターフェイスに取り出し、後で継承することでコードの重複を避けたい場合に、非常に便利です。ts)

上記の例では、PersonPlayer インターフェイスのプロパティを継承する Student インターフェイスを作成しました。

Multiple Interface Declarations

前のセクションでは、インターフェイスが他のインターフェースのプロパティを継承できる方法を学びました。 これは extend キーワードを使用して行われました。 しかし、同じモジュール (ファイル) 内で同じ名前のインターフェイスが宣言されている場合、TypeScript はプロパティ名が異なるか、競合するプロパティタイプが同じであれば、それらのプロパティをマージします。

(merged-interface.ts)

上記の例で、Person インターフェイスが複数回宣言されていることがわかります。

💡 クラスのレッスンでは、class は暗黙のうちにインタフェースを宣言し、インタフェースはそのインタフェースを拡張できることを学びました。 つまり、プログラムにクラス Person とインターフェイス Person がある場合、最終的な Person 型 (インターフェイス) はクラスとインターフェイスの間でマージされたプロパティを持ちます。

入れ子のインターフェイス

インターフェイスは深い入れ子構造を持つことが可能です。 以下の例では、Student インターフェイスの info フィールドは、firstNamelastName プロパティを持つオブジェクトの形状を定義します。

(nested-shape.ts)

同様に、あるインターフェースのフィールドは別のインターフェースの型を持つことは完全に合法的です。 次の例では、Student インターフェースの info フィールドは Person インターフェースの型を持ちます。

(nested-interface.ts)

コメントを残す

メールアドレスが公開されることはありません。