interfaceとは、オブジェクトの形状のことです。 標準的なJavaScriptのオブジェクトはkey:value
ペアのマップである。 JavaScriptオブジェクトのキーはほとんどの場合文字列で、その値はサポートされているJavaScriptの値(primitiveやabstract)です。
インターフェースはTypeScriptコンパイラに、オブジェクトが持つことのできるプロパティ名とそれに対応する値の型を伝えるものです。
プロパティ(キー)と値を持つオブジェクトを定義すると、TypeScriptはオブジェクトのプロパティ名とその値のデータ型を見て、暗黙のうちにインターフェースを作成する。 これは型推論のために起こる。
上記の例では、firstName
、lastName
、age
、getSalary
フィールドを持つオブジェクト student
を作り、いくつかの初期値を代入している。
{
firstName: string;
lastName: string;
age: number;
getSalary: (base: number) => number;
}
インターフェースはオブジェクトと同様であるが、オブジェクトのプロパティとその型に関する情報のみを含む。 また、インターフェース型を作成して名前を付けると、それを使ってオブジェクトの値に注釈を付けることができますが、ここでは暗黙的に作成されているので、このインターフェースは名前を持っていません。 前回のレッスンで、最初は暗黙的に作成され、その後、型エイリアスを使用して明示的に関数型を作成した関数型と比較できます。
定義した後に、オブジェクトのプロパティをいじってみてください。
上の例からわかるように、ross
の型が暗黙のインターフェースなのでTypeScriptはオブジェクトの形を記憶しているのだそうです。 もし、プロパティの値をインターフェイスで指定されている以外の異なる型の値で上書きしようとしたり、インターフェイスで指定されていない新しいプロパティを追加しようとすると、TypeScriptコンパイラーはプログラムをコンパイルしてくれません。
オブジェクトに基本的にどんなプロパティでも持たせたい場合は、明示的に値any
をマークすれば、割り当てられたオブジェクト値から型を推測してくれません。
Interface Declaration
The implicit interface we have been technically a type, but it wasn’t defined explicitly.TWITCH
(argument-with-shape.ts)
上記の例では、firstName
, lastName
, age
, getSalary
フィールドを持つオブジェクト引数を受け取る関数 getPersonInfo
を定義しています。 :<type>
アノテーションを使用して、プロパティ名とそれに対応する型を含むオブジェクトを型として使用していることに注意してください。 これは、インターフェイスに名前がなく、インラインで使用されているため、匿名インターフェイスの例となります。 ross
オブジェクトがさらに複雑になり、複数の場所で使用されるようになると、TypeScriptは最初は気に入っていたものの、今では扱うのが大変なものに思えてしまうのです。
上の例で、オブジェクトの形を表すインタフェース Person
を定義しましたが、今回はこの型を参照できる名前を持っているのです。 この型を使用して、ross
変数とgetPersonIfo
関数のperson
引数にアノテーションを付けています。
なぜインターフェースを使うのか?
インターフェース型は、特定の形状を強制するのに重要な場合がある。 通常 JavaScript では、オブジェクトが常に特定のプロパティを含み、そのプロパティが常に {age: 21, ...}
のような特定の型の値を持つことを実行時に盲信します。
そのプロパティがオブジェクトに存在するか、その値が期待通りであるかを最初にチェックせずに、実際にそのプロパティに対する操作を開始すると、うまくいかないことがあり、後でアプリケーションが使用できなくなることがあります。 例えば、{age: '21', ...}
、ここではage
の値がstring
.
インターフェイスは、このようなシナリオをコンパイル時に安全に処理するメカニズムを提供します。 誤って存在しないオブジェクトのプロパティを使用したり、プロパティの値を不正な操作で使用したりすると、TypeScriptコンパイラはプログラムをコンパイルしてくれません。
上記の例で、printStudent
関数内で_student
引数のname
プロパティを使ってみようとしています。 _student
引数はStudent
インターフェースの型であるため、このプロパティはStudent
インターフェースに存在しないため、TypeScriptコンパイラーはコンパイル時にエラーを投げる。
同様に、100 — _student.firstName
はfirstName
プロパティがstring
の型であり、前回調べたところ、JavaScriptではnumber
からstring
を引くことはできない(結果はNaN
)ので有効な操作ではない。
上記の例では、getSalary
フィールドに対して従来の方法で関数型を記述している。 しかし、同じように body のない関数構文を使用することもでき、これは一般的にインターフェースで使用されます。
interface Student {
firstName: string;
lastName: string;
age: number;
getSalary(base: number): number;
};
Optional Properties
時には、オブジェクトに特定のデータ型のデータを保持するプロパティが必要ですが、そのプロパティがオブジェクトにあることが必須でない場合があります。 これは、前のレッスンで学んだオプションの関数パラメータに似ています。
このようなプロパティは、オプショナルプロパティと呼ばれます。 インターフェースはオプションのプロパティを含むことができ、オプションの関数パラメータと同様に、?:Type
アノテーションを使って表現します。
上記の例では、Student
インターフェースにはage
プロパティという、オプションであるプロパティが用意されています。 しかし、age
プロパティが提供される場合、number
型の値を持たなければならない。
Student
インターフェースの型であるross
オブジェクトの場合、age
プロパティに値を提供していないので合法であるが、monica
の場合、age
プロパティは提供しているが値はstring
で合法でない。
このエラーは奇妙に見えるかもしれませんが、実は意味があるのです。 もしオブジェクトにage
プロパティが存在しなければ、object.age
はundefined
という型を返します。
したがって、age
プロパティの値は undefined
型か number
型のいずれかであり、TypeScript ではユニオン構文 number | undefined
で表される。
💡 タイプシステムのレッスンでユニオンを学ぶ。 例えば、算術演算で
age
プロパティを使用し、その値がundefined
であったとします。 これは一種の深刻な問題です。しかし、良いことに、TypeScript コンパイラーは、値が
undefined
である可能性があるので、オプショナルプロパティに対して不正な操作を行うことを許しません。ts)上記の例では、
age
プロパティに対して算術演算を行っていますが、このプロパティの値は実行時にnumber
またはundefined
になりうるため、不正な演算となっています。 しかし、上記のプログラムでは、TypeScriptのコンパイラーフラグである--strictNullChecks
フラグをfalse
に設定する必要がありました。
このエラーや警告を回避するには、このプロパティがnumber
の型であり、number
やundefined
ではないことを明示的にTypeScriptコンパイラに伝える必要があります。
上記のプログラムでは、_student.age
の型をnumber | undefined
からnumber
に変換している(_student.age as number)
を使っていますが、は型がnumber
に変換されていることを表しています。 これは、TypeScriptのコンパイラに「おい、これは数字だぞ」と伝えるための方法である。
💡 型アサーションについては、タイプシステムのレッスンで学びます。
インターフェースを使った関数型
オブジェクトの形だけでなく、インターフェースは関数のシグネチャも表すことができる。 前回のレッスンでは、関数型を記述するためにタイプエイリアスを使用しましたが、インターフェースでもそれが可能です。
interface InterfaceName {
(param: Type): Type;
}
インターフェースを関数型として宣言する構文は、関数のシグネチャそのものと似ています。 上の例からわかるように、インターフェースのボディは、もちろんボディを除いた匿名関数のシグネチャを正確に含んでいます。 ここではパラメータ名は重要ではありません。
上の例で、number
型の 2 つの引数を受け取り、boolean
値を返す関数型を定義している IsSumOdd
インターフェイスが定義されています。 IsSumOdd
インターフェース型は関数型 (x: number, y: number) => boolean
と等しいので、この型を使って関数を記述することができます。
匿名メソッドシグネチャを持つインターフェースは、関数を記述するものです。 しかし、JavaScriptの領域での関数はオブジェクトでもあり、オブジェクトと同じように関数の値にプロパティを追加することができることを意味します。
上の例で、関数を表す IsSumOdd
インターフェイスに対して type
と calculate
プロパティを追加したところ、
Object.assign
メソッドを使用して、type
とcalculate
のプロパティを関数値にマージしています。
関数型のインターフェースは、コンストラクタ関数を記述するのに役立ちます。 コンストラクタ関数は、オブジェクト(インスタンス)を作成することを仕事とするクラスに似ています。 ES5まではJavaScriptのclass
を模倣したコンストラクタ関数しかなかったのです。
💡 コンストラクタ関数について詳しく知りたい方は、こちらの記事を参考にしてください。 つまり、この関数は、通常の関数呼び出しではなく、オブジェクトを生成するために
new
キーワードを使用してのみ呼び出すことができます。 幸いなことに、コンストラクタ関数を使用する必要はありません。TypeScriptにはclass
キーワードがあり、コンストラクタ関数よりもはるかに簡単にクラスを作成できます。 実際、class
はJavaScriptではコンストラクタ関数にあたります。class Animal{
constructor( _name ) {
this.name = _name;
}
}console.log( typeof Animal ); // "function"クラスとコンストラクタ関数は同じものです。 唯一の違いは、
class
が豊富な OOP 構文を与えてくれることです。 したがって、コンストラクタ関数型のインターフェースはクラスを表します。
上の例で、型 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
型は、number
、string
、boolean
、symbol
、null
、undefined
ではない任意の値を定義します。
もし、値がプレーンな JavaScript オブジェクトであるかを厳密にチェックする必要があるなら、問題が生じるかも知れません。
interface SimpleObject {
: any;
}
SimpleObject
インターフェースは、値がany
データ型であるstring
キーを持つオブジェクトの形状を定義しています。
上の例では、SimpleObject
インターフェースのタイプでross
とmonica
オブジェクトを定義しています。 これらのオブジェクトはstring
キーとany
データ型の値を含んでいるので、完全に合法です。
もしあなたがmonica
のキー1
がnumber
型であることについて混乱しているなら、これは合法です。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
型の値を持つインデックス署名可能なインターフェース型を定義することができます。
💡 インデックス署名キー型は
string
かnumber
のどちらかでなければなりません。
上記の例で、number
型のプロパティ名とnumber
型の値を含むことができる、LapTimes
インターフェースを定義しています。 このインターフェイスは number
のキーを使ってインデックスを作成できるデータ構造を表すことができるので、配列 ross
やオブジェクト monica
および joey
は合法です。
しかし、キー one
は string
なので rachel
オブジェクトは LapTimes
に準拠せず、それ以外の string
でアクセスすることができないため、rachel
のように何もできない。
インデックス可能なインターフェース型では、いくつかのプロパティを必須、いくつかのプロパティを任意とすることが可能です。 これは、オブジェクトが特定の形状を持つ必要がある場合に非常に便利ですが、オブジェクトに余分なプロパティや不要なプロパティが含まれていても、実際には問題ではありません。ts)
上記の例では、string
値を持つプロパティname
とnumber
値を持つオプションのプロパティage
を含まなければならないインタフェースLapTimes
を定義しています。 LapTimes
型のオブジェクトは、キーが number
で値も number
でなければならない任意のプロパティを持つこともできます。
オブジェクト ross
は、オプションであるため age
プロパティを持っていなくても LapTimes
オブジェクトとして有効です。
オブジェクトjoey
はstring
の型であるgender
プロパティを持っているので、LapTimes
インタフェースに適合していません。 rachel
オブジェクトは LapTimes
インターフェースで要求される name
プロパティを持っていません。
💡 インデクサブルタイプを使うときに注意しなければならないことがいくつかあります。
Extending Interface
クラスと同様に、インターフェースは他のインターフェースからプロパティを継承することができます。 しかし、JavaScriptのクラスとは異なり、インターフェイスは複数のインターフェイスから継承することができます。
インターフェイスを継承することにより、子インターフェースは親インターフェースが持つ全てのプロパティを取得することができます。 これは、複数のインターフェイスが共通の構造を持ち、共通のプロパティを共通のインターフェイスに取り出し、後で継承することでコードの重複を避けたい場合に、非常に便利です。ts)
上記の例では、Person
と Player
インターフェイスのプロパティを継承する Student
インターフェイスを作成しました。
Multiple Interface Declarations
前のセクションでは、インターフェイスが他のインターフェースのプロパティを継承できる方法を学びました。 これは extend
キーワードを使用して行われました。 しかし、同じモジュール (ファイル) 内で同じ名前のインターフェイスが宣言されている場合、TypeScript はプロパティ名が異なるか、競合するプロパティタイプが同じであれば、それらのプロパティをマージします。
上記の例で、Person
インターフェイスが複数回宣言されていることがわかります。
💡 クラスのレッスンでは、
class
は暗黙のうちにインタフェースを宣言し、インタフェースはそのインタフェースを拡張できることを学びました。 つまり、プログラムにクラスPerson
とインターフェイスPerson
がある場合、最終的なPerson
型 (インターフェイス) はクラスとインターフェイスの間でマージされたプロパティを持ちます。
入れ子のインターフェイス
インターフェイスは深い入れ子構造を持つことが可能です。 以下の例では、Student
インターフェイスの info
フィールドは、firstName
と lastName
プロパティを持つオブジェクトの形状を定義します。
同様に、あるインターフェースのフィールドは別のインターフェースの型を持つことは完全に合法的です。 次の例では、Student
インターフェースの info
フィールドは Person
インターフェースの型を持ちます。