インターフェースとinstanceof
instanceof演算子は、オブジェクトがクラスのインスタンスかを判定するJavaScriptの演算子です。ここでは、instanceof
演算子がTypeScriptのインターフェースとどのような関係にあるのかを解説します。
他言語のinstanceof
との違い
JavaやPHPなど他の言語のinstanceof
演算子は、インターフェースに用いることができる言語もありますので、他言語の次にTypeScriptに学ぶ場合は注意してください。次はPHPでinstanceof
演算子をインターフェースに使っている例です。
PHPのinstanceof演算子の例php
interface MyInterface{}class MyClass implements MyInterface{}$a = new MyClass();var_dump($a instanceof MyInterface);//=> bool(true)
PHPのinstanceof演算子の例php
interface MyInterface{}class MyClass implements MyInterface{}$a = new MyClass();var_dump($a instanceof MyInterface);//=> bool(true)
instanceof
はインターフェースに使えない
TypeScriptは上のような言語とは異なり、instanceof インターフェイス名
で型を判定することができません。もしも、instanceof
演算子にインターフェース名を使うと、コンパイルエラーになります。
TypeScriptでinstanceof演算子を使うとコンパイルエラーになる例ts
interfaceMyInterface {}classMyClass implementsMyInterface {}consta = newMyClass ();'MyInterface' only refers to a type, but is being used as a value here.2693'MyInterface' only refers to a type, but is being used as a value here.console .log (a instanceof); MyInterface
TypeScriptでinstanceof演算子を使うとコンパイルエラーになる例ts
interfaceMyInterface {}classMyClass implementsMyInterface {}consta = newMyClass ();'MyInterface' only refers to a type, but is being used as a value here.2693'MyInterface' only refers to a type, but is being used as a value here.console .log (a instanceof); MyInterface
なぜかというと、インターフェースがTypeScript固有の機能でコンパイル時にコードから消えるためです。インターフェースは型レベルのものです。TypeScriptはJavaScriptにコンパイルするとき、型レベルのものを消します。変数の型注釈がコンパイル時に消えるのと同じ理屈です。
コンパイル時に消えるということは、JavaScript実行時にインターフェースの情報が、どこにもないということです。そのため、instanceof
がインターフェース名を取ることができないというわけです。
インターフェースの判定には型ガード関数を使う
実行時に値がインターフェースと互換しているかを判定するには、型ガード関数を用います。型ガード関数は、型を判定したい値を引数に取り、true
またはfalse
を返す関数です。たとえば、値がStudent
インターフェース型であるかを判定する関数は次のようになります。
ts
interfaceStudent {name : string;grade : number;}// Student型かを判定する型ガード関数functionisStudent (value : unknown):value isStudent {// 値がオブジェクトであるかの判定if (typeofvalue !== "object" ||value === null) {return false;}const {name ,grade } =value asRecord <keyofStudent , unknown>;// nameプロパティーが文字列型かを判定if (typeofname !== "string") {return false;}// gradeプロパティーが数値型かを判定if (typeofgrade !== "number") {return false;}return true;}
ts
interfaceStudent {name : string;grade : number;}// Student型かを判定する型ガード関数functionisStudent (value : unknown):value isStudent {// 値がオブジェクトであるかの判定if (typeofvalue !== "object" ||value === null) {return false;}const {name ,grade } =value asRecord <keyofStudent , unknown>;// nameプロパティーが文字列型かを判定if (typeofname !== "string") {return false;}// gradeプロパティーが数値型かを判定if (typeofgrade !== "number") {return false;}return true;}
そして、このisStudent
関数をinstanceof
の代わりに用いると、実行時に型の判定ができるようになります。
ts
consttom : object = {name : "Tom",grade : 2 };if (isStudent (tom )) {tom ;}
ts
consttom : object = {name : "Tom",grade : 2 };if (isStudent (tom )) {tom ;}
型ガード関数の詳細については、次のページをご覧ください。
📄️ 型ガード関数
TypeScriptのコンパイラはifやswitchといった制御フローの各場所での変数の型を分析しており、この機能を制御フロー分析(control flow analysis)と呼びます。
複雑なインターフェースの判定はzodが便利
型ガード関数の例として、上でisStudent
の実装を示しましたが、中身を見てみるとプロパティーごとに型を判定するロジックが必要なのが分かります。プロパティーが少なければ、型ガード関数の実装は短く保守可能な範囲に収まりますが、プロパティーが多くなると保守困難なコードになると想像されます。
そのようなケースで便利なのがzodです。zodはオブジェクトの構造をチェックするライブラリで、TypeScript向けに作られています。zodでは、オブジェクトの構造を定義すると、構造をチェックする型ガード関数が得られます。次は、isStudent
をzodで実装した例です。
ts
importz from "zod";// zodによるスキーマの定義conststudentSchema =z .object ({name :z .string (),grade :z .number (),});// インターフェースの型を導出typeStudent =z .infer <typeofstudentSchema >;// 型ガード関数functionisStudent (value : unknown):value isStudent {returnstudentSchema .safeParse (value ).success ;}// 型の判定consttom : object = {name : "Tom",grade : 2 };if (isStudent (tom )) {tom ;}
ts
importz from "zod";// zodによるスキーマの定義conststudentSchema =z .object ({name :z .string (),grade :z .number (),});// インターフェースの型を導出typeStudent =z .infer <typeofstudentSchema >;// 型ガード関数functionisStudent (value : unknown):value isStudent {returnstudentSchema .safeParse (value ).success ;}// 型の判定consttom : object = {name : "Tom",grade : 2 };if (isStudent (tom )) {tom ;}
zodを用いると、宣言的なコードになることで、型ガード関数の細かい実装を自分で書かなくてよくなることがわかるかと思います。プロパティーの数が多いインターフェースや、プロパティーがネストされて構造化されたインターフェースの型ガード関数が必要になった場合は、zodの導入を検討してみるといいでしょう。
抽象クラスとinstanceof
TypeScriptにはインターフェースの似たものに抽象クラスがあります。抽象クラスはインターフェースと異なり、instanceof
演算子が使えます。これは、抽象クラスはコンパイルしても、クラスとして残るためです。
ts
abstract classAbstractClass {}classConcreteClass extendsAbstractClass {}constobj = newConcreteClass ();console .log (obj instanceofAbstractClass );
ts
abstract classAbstractClass {}classConcreteClass extendsAbstractClass {}constobj = newConcreteClass ();console .log (obj instanceofAbstractClass );