メインコンテンツまでスキップ

インターフェースとinstanceof

インターフェースはTypeScriptで独自に定義された概念であり、JavaScriptには存在しません。つまりコンパイルをかけると消えてなくなります。そのため他の言語でできるようなその型が期待するインターフェースかどうかの判定ができません。上記のStudentインターフェースで次のようなことをしても実行することはできません。

ts
if (studentA instanceof Student) {
'Student' only refers to a type, but is being used as a value here.2693'Student' only refers to a type, but is being used as a value here.
// ...
}
ts
if (studentA instanceof Student) {
'Student' only refers to a type, but is being used as a value here.2693'Student' only refers to a type, but is being used as a value here.
// ...
}

これを解消するためには型ガードを自前で実装する必要があります。以下はその例のisStudent()です。

ts
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;
}
ts
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を返すと、呼び出し元では引数objStudentとして解釈されるようになります。

📄️ 型ガード関数

型ガードを使用することによってifのブロックで特定の型に絞りこむことができます。

UnknownObject

typeofで判定されるobject型はオブジェクトではあるものの、プロパティが何も定義されていない状態です。そのためそのオブジェクトがどのようなプロパティを持っているかの検査すらできません。

ts
const obj: object = {
name: "花子",
};
 
obj.name;
Property 'name' does not exist on type 'object'.2339Property 'name' does not exist on type 'object'.
ts
const obj: object = {
name: "花子",
};
 
obj.name;
Property 'name' does not exist on type 'object'.2339Property 'name' does not exist on type 'object'.

そこでインデックス型を使っていったんオブジェクトのいかなるプロパティもunknown型のオブジェクトであると型アサーションを使い解釈させます。これですべてのstring型のプロパティにアクセスできるようになります。あとは各々のunknown型のプロパティをtypeof, instanceofで判定させればこの関数の判定が正しい限りTypeScriptは引数が期待するStudentインターフェースを実装したオブジェクトであると解釈します。

関数の判定が正しい限りとは

インターフェースに変更が加わった時この関数も同時に更新されないとこの関係は崩れてしまいます。たとえばstudent.nameは現在string型ですが、これが姓名の区別のために次のようなオブジェクトに差し替えられたとします。

ts
interface Name {
surname: string;
givenName: string;
}
ts
interface Name {
surname: string;
givenName: string;
}

この変更に対しisStudent()も随伴して更新されなければこの関数がStudentインターフェースであると判定するオブジェクトのnameプロパティは明らかに違うものになるでしょう。

  • 質問する ─ 読んでも分からなかったこと、TypeScriptで分からないこと、お気軽にGitHubまで🙂
  • 問題を報告する ─ 文章やサンプルコードなどの誤植はお知らせください。