670 Stimmen

Schnittstellentypprüfung mit Typescript

Diese Frage ist das direkte Analogon zu Klassentypprüfung mit TypeScript

Ich muss zur Laufzeit herausfinden, ob eine Variable vom Typ any eine Schnittstelle implementiert. Hier ist mein Code:

interface A{
    member:string;
}

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

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

Wenn Sie diesen Code in den Typescript-Spielplatz eingeben, wird die letzte Zeile als Fehler markiert: "Der Name A existiert nicht im aktuellen Bereich". Aber das ist nicht wahr, der Name existiert im aktuellen Bereich. Ich kann sogar die Variablendeklaration ändern in var a:A={member:"foobar"}; ohne Beanstandungen seitens des Herausgebers. Nachdem ich das Web durchsucht und die andere Frage zu SO gefunden hatte, änderte ich die Schnittstelle in eine Klasse, aber dann kann ich keine Objektliterale verwenden, um Instanzen zu erstellen.

Ich fragte mich, wie der Typ A auf diese Weise verschwinden konnte, aber ein Blick auf das generierte Javascript erklärt das Problem:

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

Es gibt keine Repräsentation von A als Schnittstelle, daher sind keine Laufzeittypprüfungen möglich.

Ich verstehe, dass Javascript als dynamische Sprache kein Konzept von Schnittstellen hat. Gibt es eine Möglichkeit, Typprüfung für Schnittstellen?

Die Autovervollständigung des typescript-Spielplatzes zeigt, dass typescript sogar eine Methode anbietet implements . Wie kann ich es verwenden?

19voto

pcan Punkte 807

Es ist jetzt möglich, ich habe gerade eine verbesserte Version des TypeScript Compiler, der volle Reflexionsfähigkeit bietet. Sie können Klassen anhand ihrer Metadatenobjekte instanziieren, Metadaten von Klassenkonstruktoren abrufen und Schnittstellen/Klassen zur Laufzeit untersuchen. Sie können es ausprobieren aquí

Beispiel für die Verwendung:

Erstellen Sie in einer Ihrer Typescript-Dateien eine Schnittstelle und eine Klasse, die sie wie folgt implementiert:

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

class MyClass implements MyInterface {
    counter = 0;

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

Drucken wir nun die Liste der implementierten Schnittstellen aus.

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

mit Reflec-ts kompilieren und starten:

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

Siehe reflection.d.ts für Interface Metatyp-Details.

UPDATEです: Ein vollständiges Arbeitsbeispiel finden Sie unter aquí

17voto

DS. Punkte 20324

Hier ist eine weitere Möglichkeit: das Modul ts-interface-builder bietet ein Build-Tool, das eine TypeScript-Schnittstelle in einen Laufzeitdeskriptor umwandelt, und ts-interface-checker kann prüfen, ob ein Objekt diese Bedingung erfüllt.

Für das Beispiel von OP,

interface A {
  member: string;
}

Du würdest zuerst die ts-interface-builder die eine neue prägnante Datei mit einem Deskriptor erzeugt, sagen wir, foo-ti.ts , die Sie wie folgt verwenden können:

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" 

Sie können eine einzeilige type-guard-Funktion erstellen:

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

15voto

Anthony Gingrich Punkte 133

Fast 9 Jahre seit der OP, und das Problem bleibt bestehen. Ich möchte Typescript wirklich WIRKLICH lieben. Und normalerweise gelingt es mir auch. Aber seine Schlupflöcher in der Typensicherheit sind ein übler Geruch, den meine verkniffene Nase nicht blockieren kann.

Meine Lösungsansätze sind nicht perfekt. Aber ich bin der Meinung, dass sie besser sind als die meisten der üblicherweise verschriebenen Lösungen. Diskriminatoren haben sich als schlechte Praxis erwiesen, weil sie die Skalierbarkeit einschränken und den Zweck der Typensicherheit völlig zunichte machen. Meine 2 hübschesten und hässlichsten Lösungen sind, in dieser Reihenfolge:

Klassendekorateur: Scannt rekursiv die Mitglieder des typisierten Objekts und berechnet einen Hash auf der Grundlage der Symbolnamen. Verknüpft den Hash mit dem Typnamen in einer statischen KVP-Eigenschaft. Schließen Sie den Typnamen in die Hash-Berechnung ein, um das Risiko der Mehrdeutigkeit mit Vorfahren zu vermindern (passiert bei leeren Unterklassen). Vorteile: Es hat sich als die vertrauenswürdigste Methode erwiesen. Es

C

A

13voto

frodeborli Punkte 1363

Meiner Meinung nach ist es am besten, ein "Fubber"-Symbol an den Schnittstellen anzubringen. Es ist VIEL schneller zu schreiben, VIEL schneller für die JavaScript-Engine als ein Type Guard, unterstützt Vererbung für Schnittstellen und macht Type Guards einfach zu schreiben, wenn Sie sie brauchen.

Zu diesem Zweck gibt es in ES6 Symbole.

Schnittstelle

// Notice there is no naming conflict, because interfaces are a *type*
export const IAnimal = Symbol("IAnimal"); 
export interface IAnimal {
  [IAnimal]: boolean; // the fubber
}

export const IDog = Symbol("IDog");
export interface IDog extends IAnimal {
  [IDog]: boolean;
}

export const IHound = Symbol("IDog");
export interface IHound extends IDog {
  // The fubber can also be typed as only 'true'; meaning it can't be disabled.
  [IDog]: true;
  [IHound]: boolean;
}

Klasse

import { IDog, IAnimal } from './interfaces';
class Dog implements IDog {
  // Multiple fubbers to handle inheritance:
  [IAnimal] = true;
  [IDog] = true;
}

class Hound extends Dog implements IHound {
  [IHound] = true;
}

Prüfung

Dieser Code kann in einen Type Guard eingefügt werden, wenn Sie dem TypeScript-Compiler helfen wollen.

import { IDog, IAnimal } from './interfaces';

let dog = new Dog();

if (dog instanceof Hound || dog[IHound]) {
  // false
}
if (dog[IAnimal]?) {
  // true
}

let houndDog = new Hound();

if (houndDog[IDog]) {
  // true
}

if (dog[IDog]?) {
  // it definitely is a dog
}

11voto

Dan Dohotaru Punkte 2519

Wie oben, wobei benutzerdefinierte Schutzmaßnahmen verwendet, aber dieses Mal mit einem Pfeilfunktionsprädikat

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);

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X