web-dev-qa-db-ja.com

TypeScriptのランタイムでオブジェクトタイプを確認する方法は?

オブジェクトを関数に渡し、ランタイムでタイプをチェックする方法を見つけようとしています。これは擬似コードです:

func(obj:any){
  if(typeof obj === "A"){
    // do something
  }
  else if(typeof obj === "B"{
    //do something else
  }

}
 a:A;
 b:B;
 func(a);

しかし、typeofは常に「オブジェクト」を返すため、実際のタイプの「a」または「b」を取得する方法を見つけることができませんでした。 instanceofも機能せず、同じ結果を返しました。 TypeScriptでそれを行う方法はありますか?

ご協力ありがとうございました!!!

16
Eden1971

Edit:この質問がクラス以外の型、つまりinterfaceで定義されたオブジェクトの形状を具体的に扱っていることを、検索からここに来る人々に指摘したいまたはtypeエイリアス。クラス型の場合、JavaScriptの instanceofを使用してクラスを決定できます インスタンスが由来し、TypeScriptは型チェッカーで型を自動的に絞り込みます。

型はコンパイル時に取り除かれ、実行時には存在しないため、実行時に型を確認することはできません。

できることは、オブジェクトの形状が期待どおりであることを確認することです。TypeScriptは、コンパイル時にtrueを返す ser-defined type guard を使用して型をアサートできます(注釈付きの戻り型は "形状が期待に一致する場合は、arg is T)の形式のpredicateを入力します。

interface A {
  foo: string;
}

interface B {
  bar: number;
}

function isA(obj: any): obj is A {
  return obj.foo !== undefined 
}

function isB(obj: any): obj is B {
  return obj.bar !== undefined 
}

function func(obj: any) {
  if (isA(obj)) {
    // In this block 'obj' is narrowed to type 'A'
    obj.foo;
  }
  else if (isB(obj)) {
    // In this block 'obj' is narrowed to type 'B'
    obj.bar;
  }
}

プレイグラウンドの例

タイプガードの実装の深さは本当にあなた次第です。trueまたはfalseを返すだけで十分です。たとえば、Carlが答えで指摘しているように、上記の例では、(ドキュメントの例に従って)期待されるプロパティが定義されていることのみをチェックし、期待されるタイプが割り当てられていることをチェックしません。これは、null許容型およびネストされたオブジェクトでは扱いにくい場合があります。形状チェックの詳細度を決定するのはユーザー次第です。

29
Aaron Beall

アーロンの答えを拡張して、コンパイル時にタイプガード関数を生成するトランスフォーマーを作成しました。この方法では、それらを手動で記述する必要はありません。

例えば:

import { is } from 'TypeScript-is';

interface A {
  foo: string;
}

interface B {
  bar: number;
}

if (is<A>(obj)) {
  // obj is narrowed to type A
}

if (is<B>(obj)) {
  // obj is narrowed to type B
}

ここでプロジェクトを見つけることができます。使用方法は次のとおりです。

https://github.com/woutervh-/TypeScript-is

7
user7132587

OPの質問は、「オブジェクトを関数に渡し、ランタイムでタイプをチェックする方法を見つけようとしている」でした。

クラスインスタンスは単なるオブジェクトであるため、正しい答えは、実行時の型チェックが必要な場合はクラスインスタンスとinstanceofを使用することであり、そうでない場合はインターフェイスを使用します。

私のコードベースには通常、インターフェイスを実装するクラスがあり、インターフェイスはプリコンパイル時の型安全のためにコンパイル中に使用されますが、クラスはコードを整理するために使用され、TypeScriptでランタイム型チェックを実行します。

routerEventはNavigationStartクラスのインスタンスであるため動作します

if (routerEvent instanceof NavigationStart) {
  this.loading = true;
}

if (routerEvent instanceof NavigationEnd ||
  routerEvent instanceof NavigationCancel ||
  routerEvent instanceof NavigationError) {
  this.loading = false;
}

動作しません

// Must use a class not an interface
export interface IRouterEvent { ... }
// Fails
expect(IRouterEvent instanceof NavigationCancel).toBe(true); 

動作しません

// Must use a class not a type
export type RouterEvent { ... }
// Fails
expect(IRouterEvent instanceof NavigationCancel).toBe(true); 

上記のコードでわかるように、クラスを使用して、インスタンスをNavigationStart | Cancel | Error型と比較します。

Tsコンパイラは、コンパイルプロセス中およびJITまたはAOTで解釈される前にこれらの属性を削除するため、TypeまたはInterfaceでinstanceofを使用することはできません。クラスは、JSランタイム中だけでなくプリコンパイルにも使用できる型を作成するための優れた方法です。

0
Alpha G33k

私はアーロンからの答えで遊んでいますが、次のように、未定義の代わりにtypeofをテストする方が良いと思います:

interface A {
  foo: string;
}

interface B {
  bar: number;
}

function isA(obj: any): obj is A {
  return typeof obj.foo === 'string' 
}

function isB(obj: any): obj is B {
  return typeof obj.bar === 'number' 
}

function func(obj: any) {
  if (isA(obj)) {
    console.log("A.foo:", obj.foo);
  }
  else if (isB(obj)) {
    console.log("B.bar:", obj.bar);
  }
  else {console.log("neither A nor B")}
}

const a: A = { foo: 567 }; // notice i am giving it a number, not a string 
const b: B = { bar: 123 };

func(a);  // neither A nor B
func(b);  // B.bar: 123
0
Carl Sorenson