インターフェースとinstanceof
インターフェースはTypeScriptで独自に定義された概念であり、JavaScriptには存在しません。つまりコンパイルをかけると消えてなくなります。そのため他の言語でできるようなその型が期待するインターフェースかどうかの判定ができません。上記のStudent
インターフェースで次のようなことをしても実行することはできません。
typescript
if (studentA instanceof Student) {// ...}// 'Student' only refers to a type, but is being used as a value here.
typescript
if (studentA instanceof Student) {// ...}// 'Student' only refers to a type, but is being used as a value here.
これを解消するためには型ガードを自前で実装する必要があります。以下はその例のisStudent()
です。
typescript
type UnknownObject<T extends object> = {[P in keyof T]: unknown;};function isStudent(obj: unknown): obj is Student {if (typeof obj !== "object") {return false;}if (obj === null) {return false;}const { name, age, grade } = obj as UnknownObject<Student>;if (typeof name !== "string") {return false;}if (typeof age !== "number") {return false;}if (typeof grade !== "number") {return false;}return true;}
typescript
type UnknownObject<T extends object> = {[P in keyof T]: unknown;};function isStudent(obj: unknown): obj is Student {if (typeof obj !== "object") {return false;}if (obj === null) {return false;}const { name, age, grade } = obj as UnknownObject<Student>;if (typeof name !== "string") {return false;}if (typeof age !== "number") {return false;}if (typeof grade !== "number") {return false;}return true;}
以下はisStudent()
の解説です。
obj is Student
#
戻り値のType predicateと呼ばれる機能です。専門に解説してあるページがありますので参照ください。ここではこの関数が戻り値としてtrue
を返すと、呼び出し元では引数obj
がStudent
として解釈されるようになります。
UnknownObject
#
typeof
で判定されるobject
型はオブジェクトではあるものの、プロパティが何も定義されていない状態です。そのためそのオブジェクトがどのようなプロパティを持っているかの検査すらできません。
typescript
const obj: object = {name: "花子",};obj.name;// Property 'name' does not exist on type 'object'.
typescript
const obj: object = {name: "花子",};obj.name;// Property 'name' does not exist on type 'object'.
そこでインデックス型を使っていったんオブジェクトのいかなるプロパティもunknown
型のオブジェクトであると型アサーションを使い解釈させます。これですべてのstring
型のプロパティにアクセスできるようになります。あとは各々のunknown
型のプロパティをtypeof, instanceof
で判定させればこの関数の判定が正しい限りTypeScriptは引数が期待するStudent
インターフェースを実装したオブジェクトであると解釈します。
#
関数の判定が正しい限りとはインターフェースに変更が加わった時この関数も同時に更新されないとこの関係は崩れてしまいます。たとえばstudent.name
は現在string
型ですが、これが姓名の区別のために次のようなオブジェクトに差し替えられたとします。
typescript
interface Name {surname: string;givenName: string;}
typescript
interface Name {surname: string;givenName: string;}
この変更に対しisStudent()
も随伴して更新されなければこの関数がStudent
インターフェースであると判定するオブジェクトのname
プロパティは明らかに違うものになるでしょう。