web-dev-qa-db-ja.com

Typescriptを使ったインタフェイスタイプのチェック

この質問は TypeScriptを使ったクラス型チェック への直接の類似です。

型anyの変数がインターフェースを実装しているかどうかを実行時に確認する必要があります。これが私のコードです:

interface A{
    member:string;
}

var a:any={member:"foobar"};

if(a instanceof A) alert(a.member);

TypeScriptのプレイグラウンドでこのコードを入力すると、最後の行に「現在のスコープに名前Aは存在しません」というエラーが表示されます。しかし、そうではありません。名前は現在のスコープ内に存在します。エディタからの苦情なしに、変数宣言をvar a:A={member:"foobar"};に変更することさえできます。 WebをブラウズしてSOに関する他の質問を見つけた後、インターフェイスをクラスに変更しましたが、インスタンスを作成するためにオブジェクトリテラルを使用することはできません。

私はどのようにタイプAがそのように消えることができるか疑問に思いました、しかし生成されたJavaScriptを見ることは問題を説明します:

var a = {
    member: "foobar"
};
if(a instanceof A) {
    alert(a.member);
}

インタフェースとしてのAの表現はないので、ランタイム型チェックは不可能です。

動的言語としてのJavaScriptにはインターフェースという概念がないことを私は理解しています。インターフェイスのタイプチェックをする方法はありますか?

TypeScriptの遊び場の自動補完は、TypeScriptがimplementsというメソッドさえ提供することを明らかにしています。どうやって使えますか?

188
lhk

あなたは今カスタム型ガードを書くことができるようにinstanceofキーワードなしであなたが望むものを達成することができます:

interface A{
    member:string;
}

function instanceOfA(object: any): object is A {
    return 'member' in object;
}

var a:any={member:"foobar"};

if (instanceOfA(a)) {
    alert(a.member);
}

たくさんのメンバー

オブジェクトが自分のタイプに一致するかどうかを判断するために多数のメンバをチェックする必要がある場合は、代わりに識別子を追加することができます。下記は最も基本的な例です、そしてあなたはあなた自身の識別子を管理する必要があります…あなたは重複する識別子を避けることを保証するためにパターンをより深く理解する必要があるでしょう。

interface A{
    discriminator: 'I-AM-A';
    member:string;
}

function instanceOfA(object: any): object is A {
    return object.discriminator === 'I-AM-A';
}

var a:any = {discriminator: 'I-AM-A', member:"foobar"};

if (instanceOfA(a)) {
    alert(a.member);
}
132
Fenton

TypeScript 1.6では、 ユーザー定義のタイプガード が実行されます。

interface Foo {
    fooProperty: string;
}

interface Bar {
    barProperty: string;
}

function isFoo(object: any): object is Foo {
    return 'fooProperty' in object;
}

let object: Foo | Bar;

if (isFoo(object)) {
    // `object` has type `Foo`.
    object.fooProperty;
} else {
    // `object` has type `Bar`.
    object.barProperty;
}

Joe Yangが述べたように、TypeScript 2.0以降では、タグ付き共用体型を利用することもできます。

interface Foo {
    type: 'foo';
    fooProperty: string;
}

interface Bar {
    type: 'bar';
    barProperty: number;
}

let object: Foo | Bar;

// You will see errors if `strictNullChecks` is enabled.
if (object.type === 'foo') {
    // object has type `Foo`.
    object.fooProperty;
} else {
    // object has type `Bar`.
    object.barProperty;
}

そしてそれはswitchでも動作します。

66
vilicvane

TypeScript 2.0ではタグ付き共用体が導入されました

TypeScript 2.0の機能

interface Square {
    kind: "square";
    size: number;
}

interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}

interface Circle {
    kind: "circle";
    radius: number;
}

type Shape = Square | Rectangle | Circle;

function area(s: Shape) {
    // In the following switch statement, the type of s is narrowed in each case clause
    // according to the value of the discriminant property, thus allowing the other properties
    // of that variant to be accessed without a type assertion.
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.width * s.height;
        case "circle": return Math.PI * s.radius * s.radius;
    }
}
33
Joe Yang

ユーザー定義型ガードはどうですか? https://www.typescriptlang.org/docs/handbook/advanced-types.html

interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function isFish(pet: Fish | Bird): pet is Fish { //magic happens here
    return (<Fish>pet).swim !== undefined;
}

// Both calls to 'swim' and 'fly' are now okay.

if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}
22

それが可能になりました。私は完全なリフレクション機能を提供するTypeScriptコンパイラの拡張版をリリースしました。メタデータオブジェクトからクラスをインスタンス化し、クラスコンストラクタからメタデータを取得し、実行時にインタフェースやクラスを調べることができます。あなたはそれをチェックアウトすることができます ここ

使用例

TypeScriptファイルのいずれかで、次のようにインターフェイスとそれを実装するクラスを作成します。

interface MyInterface {
    doSomething(what: string): number;
}

class MyClass implements MyInterface {
    counter = 0;

    doSomething(what: string): number {
        console.log('Doing ' + what);
        return this.counter++;
    }
}

それでは、実装されたインターフェースのリストをいくつか印刷しましょう。

for (let classInterface of MyClass.getClass().implements) {
    console.log('Implemented interface: ' + classInterface.name)
}

reflec-tsでコンパイルして起動します。

$ node main.js
Implemented interface: MyInterface
Member name: counter - member kind: number
Member name: doSomething - member kind: function

Interfaceメタタイプの詳細についてはreflection.d.tsを参照してください。

UPDATE:完全な動作例を見つけることができます ここ

14
pcan

これは別のオプションです。モジュール ts-interface-builder はTypeScriptインターフェースをランタイム記述子に変換するビルド時ツールを提供します、そして ts-interface-checker オブジェクトはそれを満たします。

OPの例では、

interface A {
  member: string;
}

最初にts-interface-builderを実行して、記述子付きの新しい簡潔なファイル、たとえばfoo-ti.tsを作成します。これは次のように使用できます。

import fooDesc from './foo-ti.ts';
import {createCheckers} from "ts-interface-checker";
const {A} = createCheckers(fooDesc);

A.check({member: "hello"});           // OK
A.check({member: 17});                // Fails with ".member is not a string" 

ワンライナータイプガード関数を作成することができます。

function isA(value: any): value is A { return A.test(value); }
6
DS.

TypeScriptは、オブジェクトが特定のインターフェースを実装しているかどうかを動的にテストするための直接的なメカニズムを提供していません。

代わりに、TypeScriptコードは、適切なメンバーのセットがオブジェクト上に存在するかどうかをチェックするJavaScriptの手法を使用できます。例えば:

var obj : any = new Foo();

if (obj.someInterfaceMethod) {
    ...
}
5
Daniel Ribeiro

上記と同じ、 ユーザー定義ガード が使用されていますが、今回は矢印関数の述語が使用されています。

interface A {
  member:string;
}

const check = (p: any): p is A => p.hasOwnProperty('member');

var foo: any = { member: "foobar" };
if (check(foo))
    alert(foo.member);
4
Dan Dohotaru

TypeGuards

interface MyInterfaced {
    x: number
}

function isMyInterfaced(arg: any): arg is MyInterfaced {
    return arg.x !== undefined;
}

if (isMyInterfaced(obj)) {
    (obj as MyInterfaced ).x;
}
3
Dmitry Matveev

型は実行時に未知であるため、未知のオブジェクトを型に対してではなく、既知の型のオブジェクトに対して比較するために、次のようにコードを記述しました。

  1. 正しい種類のサンプルオブジェクトを作成します。
  2. その要素のどれがオプションかを指定します
  3. このサンプルオブジェクトに対して未知のオブジェクトを詳細に比較します。

これが私がディープ比較のために使う(インターフェースにとらわれない)コードです:

function assertTypeT<T>(loaded: any, wanted: T, optional?: Set<string>): T {
  // this is called recursively to compare each element
  function assertType(found: any, wanted: any, keyNames?: string): void {
    if (typeof wanted !== typeof found) {
      throw new Error(`assertType expected ${typeof wanted} but found ${typeof found}`);
    }
    switch (typeof wanted) {
      case "boolean":
      case "number":
      case "string":
        return; // primitive value type -- done checking
      case "object":
        break; // more to check
      case "undefined":
      case "symbol":
      case "function":
      default:
        throw new Error(`assertType does not support ${typeof wanted}`);
    }
    if (Array.isArray(wanted)) {
      if (!Array.isArray(found)) {
        throw new Error(`assertType expected an array but found ${found}`);
      }
      if (wanted.length === 1) {
        // assume we want a homogenous array with all elements the same type
        for (const element of found) {
          assertType(element, wanted[0]);
        }
      } else {
        // assume we want a Tuple
        if (found.length !== wanted.length) {
          throw new Error(
            `assertType expected Tuple length ${wanted.length} found ${found.length}`);
        }
        for (let i = 0; i < wanted.length; ++i) {
          assertType(found[i], wanted[i]);
        }
      }
      return;
    }
    for (const key in wanted) {
      const expectedKey = keyNames ? keyNames + "." + key : key;
      if (typeof found[key] === 'undefined') {
        if (!optional || !optional.has(expectedKey)) {
          throw new Error(`assertType expected key ${expectedKey}`);
        }
      } else {
        assertType(found[key], wanted[key], expectedKey);
      }
    }
  }

  assertType(loaded, wanted);
  return loaded as T;
}

下記は私がそれを使用する方法の例です。

この例では、JSONにタプルの配列が含まれており、そのうちの2番目の要素はUserという名前のインタフェースのインスタンスです(2つのオプション要素があります)。

TypeScriptの型チェックは私のサンプルオブジェクトが正しいことを保証します、そしてassertTypeT関数は未知の(JSONからロードされた)オブジェクトがサンプルオブジェクトと一致することをチェックします。

export function loadUsers(): Map<number, User> {
  const found = require("./users.json");
  const sample: [number, User] = [
    49942,
    {
      "name": "ChrisW",
      "email": "[email protected]",
      "gravatarHash": "75bfdecf63c3495489123fe9c0b833e1",
      "profile": {
        "location": "Normandy",
        "aboutMe": "I wrote this!\n\nFurther details are to be supplied ..."
      },
      "favourites": []
    }
  ];
  const optional: Set<string> = new Set<string>(["profile.aboutMe", "profile.location"]);
  const loaded: [number, User][] = assertTypeT(found, [sample], optional);
  return new Map<number, User>(loaded);
}

ユーザー定義型ガードの実装では、このようにチェックを呼び出すことができます。

0
ChrisW

Fentonの answer に基づいて、特定のobjectinterfaceのキーを完全にまたは部分的に持っているかどうかを検証する関数の実装を次に示します。

ユースケースによっては、各インターフェイスのプロパティのタイプも確認する必要があります。以下のコードはそれを行いません。

function implementsTKeys<T>(obj: any, keys: (keyof T)[]): obj is T {
    if (!obj || !Array.isArray(keys)) {
        return false;
    }

    const implementKeys = keys.reduce((impl, key) => impl && key in obj, true);

    return implementKeys;
}

使用例:

interface A {
    propOfA: string;
    methodOfA: Function;
}

let objectA: any = { propOfA: '' };

// Check if objectA partially implements A
let implementsA = implementsTKeys<A>(objectA, ['propOfA']);

console.log(implementsA); // true

objectA.methodOfA = () => true;

// Check if objectA fully implements A
implementsA = implementsTKeys<A>(objectA, ['propOfA', 'methodOfA']);

console.log(implementsA); // true

objectA = {};

// Check again if objectA fully implements A
implementsA = implementsTKeys<A>(objectA, ['propOfA', 'methodOfA']);

console.log(implementsA); // false, as objectA now is an empty object
0
aledpardo