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?

3voto

Björn Hjorth Punkte 2388

Typenschutz in Typescript mit Reflect

Hier ist ein Beispiel für einen Type Guard aus meiner Typescript-Spielengine

 export interface Start {
    /**
     * Start is called on the frame when a script is enabled just before any of the Update methods are called the first time.
     */
     start(): void
 }

/**
  * User Defined Type Guard for Start
  */
 export const implementsStart = (arg: any): arg is Start => {
     return Reflect.has(arg, 'start')
 } 

 /**
  * Example usage of the type guard
  */

 start() {
    this.components.forEach(component => {
        if (implementsStart(component)) {
            component.start()
        }  

    })
}

2voto

Arnold Vakaria Punkte 189

Eine andere Lösung könnte etwas Ähnliches sein, wie es bei der HTMLIFrameElement-Schnittstelle verwendet wird. Wir können eine Variable mit demselben Namen deklarieren, indem wir ein Objekt über die Schnittstelle erstellen, wenn wir wissen, dass es eine Implementierung dafür in einem anderen Modul gibt.

declare var HTMLIFrameElement: {
    prototype: HTMLIFrameElement;
    new(): HTMLIFrameElement;
};

In dieser Situation also

interface A {
    member:string;
}

declare var A : {
    prototype: A;
    new(): A;
};

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

sollte gut funktionieren

2voto

Tristan Punkte 1

Nicht getestet...

interface MySuperInterface {}

interface myInterface extends MySuperInterface {
    myObjectVariable: any
}

if ((myObject as MyInterface).myObjectVariable !== undefined)

2voto

Botond Punkte 622
export interface ConfSteps {
    group: string;
    key: string;
    steps: string[];
}

private verify(): void {
    const obj = `{
      "group": "group",
      "key": "key",
      "steps": [],
      "stepsPlus": []
    } `;
    if (this.implementsObject<ConfSteps>(obj, ['group', 'key', 'steps'])) {
      console.log(`Implements ConfSteps: ${obj}`);
    }
  }

private objProperties: Array<string> = [];

private implementsObject<T>(obj: any, keys: (keyof T)[]): boolean {
    JSON.parse(JSON.stringify(obj), (key, value) => {
      this.objProperties.push(key);
    });
    for (const key of keys) {
      if (!this.objProperties.includes(key.toString())) {
        return false;
      }
    }
    this.objProperties = null;
    return true;
  }

1voto

John Miller Punkte 475

Diese Antwort ist sehr einfach. Allerdings ist diese Lösung in vielleicht 3/4 der Fälle zumindest möglich (wenn auch nicht immer ideal). Mit anderen Worten, dies ist wahrscheinlich für jeden, der dies liest, relevant.

Nehmen wir an, ich habe eine sehr einfache Funktion, die den Schnittstellentyp eines Parameters kennen muss:

const simpleFunction = (canBeTwoInterfaces: interfaceA | interface B) => { 
  // if interfaceA, then return canBeTwoInterfaces.A
  // if interfaceB, then return canBeTwoInterfaces.B
}

Die Antworten, die die meisten Bewertungen erhalten, tendieren dazu, die "Funktionsprüfung" zu verwenden, d. h.,

const simpleFunction = (canBeTwoInterfaces: interfaceA | interface B) => { 
  if (canBeTwoInterfaces.onlyExistsOnInterfaceA) return canBeTwoInterfaces.A
  else return canBeTwoInterfaces.B
}

In der Codebasis, mit der ich arbeite, bestehen die Schnittstellen, die ich überprüfen muss, jedoch meist aus optionalen Parametern. Außerdem könnte jemand aus meinem Team die Namen plötzlich ändern, ohne dass ich es merke. Wenn das nach der Codebasis klingt, in der Sie arbeiten, dann ist die folgende Funktion viel sicherer.

Wie ich bereits sagte, mag dies vielen als eine sehr offensichtliche Sache erscheinen. Dennoch ist es nicht selbstverständlich, zu wissen, wann und wo eine bestimmte Lösung anzuwenden ist, unabhängig davon, ob es sich um eine brutal einfache Lösung wie die folgende handelt.

Ich würde Folgendes tun:

const simpleFunction = (
  canBeTwoInterfaces: interfaceA | interface B,
  whichInterfaceIsIt: 'interfaceA' | 'interfaceB'
) => { 
  if (whichInterfaceIsIt === 'interfaceA') return canBeTwoInterface.A
  else return canBeTwoInterfaces.B
}

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