TypeScript インターフェース 実行時チェック
背景
TypeScriptはJavaScriptのスーパーセットであり、静的な型付けを提供します。インターフェースは、オブジェクトが持つべきプロパティとメソッドの型を定義するためのものです。
チェックの方法
実行時にオブジェクトがインターフェースを実装しているかどうかをチェックするには、以下の方法を使用します。
instanceof演算子
interface MyInterface {
property: string;
method(): void;
}
class MyClass implements MyInterface {
property = "value";
method() {
console.log("method called");
}
}
const instance = new MyClass();
if (instance instanceof MyInterface) {
console.log("instance implements MyInterface");
} else {
console.log("instance does not implement MyInterface");
}
この方法では、instanceof
演算子を使用して、オブジェクトがインターフェースのコンストラクタからインスタンス化されたかどうかをチェックします。
typeof演算子
interface MyInterface {
property: string;
method(): void;
}
const instance: MyInterface = {
property: "value",
method() {
console.log("method called");
}
};
if (typeof instance === "object" && instance !== null && "property" in instance && "method" in instance) {
console.log("instance implements MyInterface");
} else {
console.log("instance does not implement MyInterface");
}
この方法では、typeof
演算子を使用して、オブジェクトの型をチェックし、必要なプロパティとメソッドが存在するかを確認します。
注意点
typeof
演算子は、オブジェクトの型をチェックし、必要なプロパティとメソッドが存在するかを確認します。そのため、インターフェースの型を持たないオブジェクトでも、必要なプロパティとメソッドが存在する場合にチェックが成功する可能性があります。instanceof
演算子は、オブジェクトがインターフェースのコンストラクタからインスタンス化されたかどうかをチェックします。そのため、インターフェースを直接実装していないオブジェクトでも、インターフェースの型を持つ場合にチェックが成功する可能性があります。
TypeScript で実行時にオブジェクトがインターフェースを実装しているかチェックする例
interface MyInterface {
property: string;
method(): void;
}
class MyClass implements MyInterface {
property = "value";
method() {
console.log("method called");
}
}
const instance = new MyClass();
// MyClassがMyInterfaceを実装しているので、true
if (instance instanceof MyInterface) {
console.log("instance implements MyInterface");
} else {
console.log("instance does not implement MyInterface");
}
- 解説
instanceof
演算子は、左側のオブジェクトが右側の型のインスタンスかどうかを判定します。- この例では、
instance
がMyInterface
のインスタンスかどうかをチェックしています。 MyClass
がMyInterface
を実装しているので、true
となり、"instance implements MyInterface" が出力されます。
typeof 演算子を使った例
interface MyInterface {
property: string;
method(): void;
}
const instance: MyInterface = {
property: "value",
method() {
console.log("method called");
}
};
// オブジェクトの型と必要なプロパティ、メソッドが存在するかをチェック
if (typeof instance === "object" && instance !== null && "property" in instance && "method" in instance) {
console.log("instance implements MyInterface");
} else {
console.log("instance does not implement MyInterface");
}
- 解説
typeof
演算子でオブジェクトの型をobject
であることを確認します。null
でないことを確認します。property
とmethod
というプロパティが存在するかを確認します。- 上記の条件をすべて満たせば、
instance
がMyInterface
の構造を持っていると判断できます。
型ガード関数を使った例
interface MyInterface {
property: string;
method(): void;
}
function isMyInterface(obj: any): obj is MyInterface {
return typeof obj === "object" && obj !== null && "property" in obj && "method" in obj;
}
const instance: any = {
property: "value",
method() {
console.log("method called");
}
};
if (isMyInterface(instance)) {
console.log("instance implements MyInterface");
instance.method(); // 型が確定しているので、instance.method() を安全に呼び出せる
}
- 解説
isMyInterface
という型ガード関数を定義します。- 型ガード関数は、引数のオブジェクトが特定の型を持つかどうかを判定し、その結果を返す関数です。
- この関数では、
typeof
演算子を使ってオブジェクトの型と必要なプロパティの存在を確認しています。 - 型ガード関数の戻り値の型アノテーション
obj is MyInterface
は、関数がtrue
を返した場合、引数の型がMyInterface
に絞り込まれることを示します。
どちらの方法を使うべきか
- 型ガード関数
より柔軟な型チェックが必要な場合や、カスタムの型ガードロジックを実装したい場合に適しています。 - typeof
インターフェースの構造と一致しているかどうかを判定したい場合に適しています。 - instanceof
クラスのインスタンスかどうかを正確に判定したい場合に適しています。
instanceof
はクラスとの関係で使うことが多いですが、インターフェースとの関係で使うことも可能です。ただし、インターフェース自体がインスタンス化されるわけではないため、注意が必要です。- 実行時チェックは、動的な処理や外部から渡されるデータの型を検証する場合などに利用されます。
- TypeScript のインターフェースは、実行時の型チェックではなく、コンパイル時の型チェックが主な目的です。
TypeScript で実行時にインターフェースの実装をチェックする代替方法
これまで instanceof
演算子と typeof
演算子、そして型ガード関数を使った方法をご紹介しましたが、他にもいくつかの方法で実行時にオブジェクトがインターフェースを実装しているかどうかをチェックすることができます。
ユーザー定義型ガード関数
より詳細な型チェックを行うために、ユーザー定義の型ガード関数を作成することができます。
function isMyInterface(obj: any): obj is MyInterface {
return typeof obj === 'object' &&
obj !== null &&
'property' in obj && typeof obj.property === 'string' &&
'method' in obj && typeof obj.method === 'function';
}
この例では、property
が string
型であり、method
が function
型であることを追加でチェックしています。
TypeScript 4.2 以降の型述語
TypeScript 4.2 以降では、型述語がより柔軟になりました。
function isMyInterface(obj: any): obj is MyInterface {
return 'property' in obj && typeof obj.method === 'function';
}
この例では、property
と method
の存在のみをチェックしています。
utility-types を利用した型ガード
utility-types
ライブラリを利用することで、より簡潔に型ガードを作成できます。
import { TypeGuard } from 'utility-types';
type MyInterfaceGuard = TypeGuard<MyInterface>;
const isMyInterface: MyInterfaceGuard = (obj): obj is MyInterface => {
// ...
};
Intersection Types
Intersection Types を利用して、複数のインターフェースを実装しているかどうかをチェックすることも可能です。
interface AnotherInterface {
anotherProperty: number;
}
function isMyInterfaceAndAnotherInterface(obj: any): obj is MyInterface & AnotherInterface {
// ...
}
Mapped Types
Mapped Types を利用して、特定のパターンを持つオブジェクトを検出することもできます。
type HasProperty<T, K extends PropertyKey> = T extends { [P in K]: unknown } ? true : false;
function hasProperty<T, K extends PropertyKey>(obj: T, key: K): obj is HasProperty<T, K> {
return key in obj;
}
サードパーティライブラリ
io-ts
や zod
などのサードパーティライブラリを利用することで、より強力な型チェックとバリデーションを行うことができます。
どの方法を選ぶべきか?
- 高度なバリデーション
サードパーティライブラリ - 型安全性を高めたい
utility-types、Intersection Types、Mapped Types - 詳細な型チェック
ユーザー定義型ガード関数、型述語 - シンプルで一般的なチェック
instanceof
やtypeof
TypeScript で実行時にインターフェースの実装をチェックする方法は、上記以外にも様々な方法があります。どの方法を選ぶかは、プロジェクトの規模、コードの複雑さ、必要なチェックの厳密さなどによって異なります。
- パフォーマンス
複雑な型チェックはパフォーマンスに影響を与える可能性があります。 - false positive/negative
型チェックは完璧ではなく、誤検知が発生する可能性があります。 - 実行時チェックはオーバーヘッド
コンパイル時の型チェックが基本であり、実行時チェックはあくまで補助的なものです。
- 条件分岐
if
文などで条件分岐を行い、型の異なる値に対して異なる処理を行うことができます。 - 型アサーション
型アサーションは型チェックを回避する方法ですが、誤った使用はバグの原因となるため、慎重に使用する必要があります。
javascript typescript