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

インターフェースと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
interface MyInterface {}
 
class MyClass implements MyInterface {}
 
const a = new MyClass();
console.log(a instanceof MyInterface);
'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.
TypeScriptでinstanceof演算子を使うとコンパイルエラーになる例
ts
interface MyInterface {}
 
class MyClass implements MyInterface {}
 
const a = new MyClass();
console.log(a instanceof MyInterface);
'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.

なぜかというと、インターフェースがTypeScript固有の機能でコンパイル時にコードから消えるためです。インターフェースは型レベルのものです。TypeScriptはJavaScriptにコンパイルするとき、型レベルのものを消します。変数の型注釈がコンパイル時に消えるのと同じ理屈です。

コンパイル時に消えるということは、JavaScript実行時にインターフェースの情報が、どこにもないということです。そのため、instanceofがインターフェース名を取ることができないというわけです。

インターフェースの判定には型ガード関数を使う

実行時に値がインターフェースと互換しているかを判定するには、型ガード関数を用います。型ガード関数は、型を判定したい値を引数に取り、trueまたはfalseを返す関数です。たとえば、値がStudentインターフェース型であるかを判定する関数は次のようになります。

ts
interface Student {
name: string;
grade: number;
}
 
// Student型かを判定する型ガード関数
function isStudent(value: unknown): value is Student {
// 値がオブジェクトであるかの判定
if (typeof value !== "object" || value === null) {
return false;
}
const { name, grade } = value as Record<keyof Student, unknown>;
// nameプロパティーが文字列型かを判定
if (typeof name !== "string") {
return false;
}
// gradeプロパティーが数値型かを判定
if (typeof grade !== "number") {
return false;
}
return true;
}
ts
interface Student {
name: string;
grade: number;
}
 
// Student型かを判定する型ガード関数
function isStudent(value: unknown): value is Student {
// 値がオブジェクトであるかの判定
if (typeof value !== "object" || value === null) {
return false;
}
const { name, grade } = value as Record<keyof Student, unknown>;
// nameプロパティーが文字列型かを判定
if (typeof name !== "string") {
return false;
}
// gradeプロパティーが数値型かを判定
if (typeof grade !== "number") {
return false;
}
return true;
}

そして、このisStudent関数をinstanceofの代わりに用いると、実行時に型の判定ができるようになります。

ts
const tom: object = { name: "Tom", grade: 2 };
const tom: object
if (isStudent(tom)) {
tom;
const tom: Student
}
ts
const tom: object = { name: "Tom", grade: 2 };
const tom: object
if (isStudent(tom)) {
tom;
const tom: Student
}

型ガード関数の詳細については、次のページをご覧ください。

📄️ 型ガード関数

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

複雑なインターフェースの判定はzodが便利

型ガード関数の例として、上でisStudentの実装を示しましたが、中身を見てみるとプロパティーごとに型を判定するロジックが必要なのが分かります。プロパティーが少なければ、型ガード関数の実装は短く保守可能な範囲に収まりますが、プロパティーが多くなると保守困難なコードになると想像されます。

そのようなケースで便利なのがzodです。zodはオブジェクトの構造をチェックするライブラリで、TypeScript向けに作られています。zodでは、オブジェクトの構造を定義すると、構造をチェックする型ガード関数が得られます。次は、isStudentをzodで実装した例です。

ts
import z from "zod";
 
// zodによるスキーマの定義
const studentSchema = z.object({
name: z.string(),
grade: z.number(),
});
// インターフェースの型を導出
type Student = z.infer<typeof studentSchema>;
type Student = { name: string; grade: number; }
// 型ガード関数
function isStudent(value: unknown): value is Student {
return studentSchema.safeParse(value).success;
}
// 型の判定
const tom: object = { name: "Tom", grade: 2 };
if (isStudent(tom)) {
tom;
const tom: { name: string; grade: number; }
}
ts
import z from "zod";
 
// zodによるスキーマの定義
const studentSchema = z.object({
name: z.string(),
grade: z.number(),
});
// インターフェースの型を導出
type Student = z.infer<typeof studentSchema>;
type Student = { name: string; grade: number; }
// 型ガード関数
function isStudent(value: unknown): value is Student {
return studentSchema.safeParse(value).success;
}
// 型の判定
const tom: object = { name: "Tom", grade: 2 };
if (isStudent(tom)) {
tom;
const tom: { name: string; grade: number; }
}

zodを用いると、宣言的なコードになることで、型ガード関数の細かい実装を自分で書かなくてよくなることがわかるかと思います。プロパティーの数が多いインターフェースや、プロパティーがネストされて構造化されたインターフェースの型ガード関数が必要になった場合は、zodの導入を検討してみるといいでしょう。

抽象クラスとinstanceof

TypeScriptにはインターフェースの似たものに抽象クラスがあります。抽象クラスはインターフェースと異なり、instanceof演算子が使えます。これは、抽象クラスはコンパイルしても、クラスとして残るためです。

ts
abstract class AbstractClass {}
class ConcreteClass extends AbstractClass {}
const obj = new ConcreteClass();
console.log(obj instanceof AbstractClass);
true
ts
abstract class AbstractClass {}
class ConcreteClass extends AbstractClass {}
const obj = new ConcreteClass();
console.log(obj instanceof AbstractClass);
true