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?

1voto

Evan Crooks Punkte 41

Sie können auch mehrere Eingaben an untergeordnete Komponenten senden, wobei eine ein Diskriminator und die andere die eigentlichen Daten ist, und den Diskriminator in der untergeordneten Komponente wie folgt überprüfen:

@Input() data?: any;
@Input() discriminator?: string;

ngOnInit(){
    if(this.discriminator = 'InterfaceAName'){
      //do stuff
    }
    else if(this.discriminator = 'InterfaceBName'){
      //do stuff
    }
}

Natürlich können Sie dies in wo auch immer es anwendbar ist zu verwenden, wie eine ngOnChanges Funktion oder eine Setter-Funktion, aber die Idee steht noch. Ich würde auch empfehlen, zu versuchen, ein ngModel mit den Eingabedaten zu verknüpfen, wenn Sie ein reaktives Formular wünschen. Sie können diese if-Anweisungen verwenden, um das ngModel auf der Grundlage der übergebenen Daten einzustellen, und spiegeln das in der HTML mit entweder:

<div [(ngModel)]={{dataModel}}>
    <div *ngFor="let attr of (data | keyvalue)">
        <!--You can use attr.key and attr.value in this situation to display the attributes of your interface, and their associated values from the data -->
    </div>
</div>

Oder stattdessen dies:

<div *ngIf = "model == 'InterfaceAName'">
    <div>Do This Stuff</div>
</div>
<div *ngIf= "model == 'IntefaceBName'">
    <div>Do this instead</div>
</div>

(Sie können attr.key und attr.value in dieser Situation verwenden, um die Attribute Ihrer Schnittstelle und ihre zugehörigen Werte aus den Daten anzuzeigen)

Ich weiß, die Frage ist bereits beantwortet, aber ich dachte, dies könnte nützlich sein für Menschen, die versuchen, semi-ambiguous angular Formen zu bauen. Sie können dies auch für angulare Materialmodule (z. B. Dialogfelder) verwenden, indem Sie zwei Variablen über den Datenparameter senden - eine davon sind Ihre tatsächlichen Daten, die andere ist ein Unterscheidungsmerkmal - und sie durch einen ähnlichen Prozess überprüfen. Letztendlich können Sie auf diese Weise ein Formular erstellen und das Formular um die Daten herum formen, die in das Formular einfließen.

0voto

troYman Punkte 1162

Die Arbeit mit String-Literalen ist schwierig, denn wenn Sie Ihre Methoden- oder Schnittstellennamen refaktorisieren wollen, kann es sein, dass Ihre IDE diese String-Literale nicht refaktorisiert. Ich biete Ihnen meine Lösung an, die funktioniert, wenn es mindestens eine Methode in der Schnittstelle gibt

export class SomeObject implements interfaceA {
  public methodFromA() {}
}

export interface interfaceA {
  methodFromA();
}

Prüfen Sie, ob das Objekt vom Typ Schnittstelle ist:

const obj = new SomeObject();
const objAsAny = obj as any;
const objAsInterfaceA = objAsAny as interfaceA;
const isObjOfTypeInterfaceA = objAsInterfaceA.methodFromA != null;
console.log(isObjOfTypeInterfaceA)

Hinweis: Wir erhalten "true", auch wenn wir "implements interfaceA" entfernen, da die Methode in der Klasse SomeObject weiterhin existiert.

0voto

Jack Miller Punkte 5527

Einfache Behelfslösung mit den gleichen Nachteilen wie die gewählte Lösung Diese Variante fängt jedoch JS-Fehler ab, akzeptiert nur Objekte als Parameter und hat einen sinnvollen Rückgabewert.

interface A{
    member:string;
}

const implementsA = (o: object): boolean => {
    try {
        return 'member' in o;
    } catch (error) {
        return false;
    }
}

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

implementsA(a) && console.log("a implements A");
// implementsA("str"); // causes TS transpiler error

0voto

Faliorn Punkte 1239

Ich weiß, die Frage ist schon etwas älter, aber das sind nur meine 50 Cent. Das hat bei mir funktioniert:

const container: Container = icc.controlComponent as unknown as Container;
if (container.getControlComponents) {
    this.allControlComponents.push(...container.getControlComponents());
}

Container ist die Schnittstelle, und icc.controlComponent ist das Objekt, das ich überprüfen wollte, und getControlComponents ist eine Methode aus Container Schnittstelle.

-1voto

Ledom Punkte 1

Hier ist die Lösung, die ich mit Hilfe von Klassen und lodash : (es funktioniert!)

// TypeChecks.ts
import _ from 'lodash';

export class BakedChecker {
    private map: Map<string, string>;

    public constructor(keys: string[], types: string[]) {
        this.map = new Map<string, string>(keys.map((k, i) => {
            return [k, types[i]];
        }));
        if (this.map.has('__optional'))
            this.map.delete('__optional');
    }

    getBakedKeys() : string[] {
        return Array.from(this.map.keys());
    }

    getBakedType(key: string) : string {
        return this.map.has(key) ? this.map.get(key) : "notfound";
    }
}

export interface ICheckerTemplate {
    __optional?: any;
    [propName: string]: any;
}

export function bakeChecker(template : ICheckerTemplate) : BakedChecker {
    let keys = _.keysIn(template);
    if ('__optional' in template) {
        keys = keys.concat(_.keysIn(template.__optional).map(k => '?' + k));
    }
    return new BakedChecker(keys, keys.map(k => {
        const path = k.startsWith('?') ? '__optional.' + k.substr(1) : k;
        const val = _.get(template, path);
        if (typeof val === 'object') return val;
        return typeof val;
    }));
}

export default function checkType<T>(obj: any, template: BakedChecker) : obj is T {
    const o_keys = _.keysIn(obj);
    const t_keys = _.difference(template.getBakedKeys(), ['__optional']);
    return t_keys.every(tk => {
        if (tk.startsWith('?')) {
            const ak = tk.substr(1);
            if (o_keys.includes(ak)) {
                const tt = template.getBakedType(tk);
                if (typeof tt === 'string')
                    return typeof _.get(obj, ak) === tt;
                else {
                    return checkType<any>(_.get(obj, ak), tt);
                }
            }
            return true;
        }
        else {
            if (o_keys.includes(tk)) {
                const tt = template.getBakedType(tk);
                if (typeof tt === 'string')
                    return typeof _.get(obj, tk) === tt;
                else {
                    return checkType<any>(_.get(obj, tk), tt);
                }
            }
            return false;
        }
    });
}

benutzerdefinierte Klassen:

// MyClasses.ts

import checkType, { bakeChecker } from './TypeChecks';

class Foo {
    a?: string;
    b: boolean;
    c: number;

    public static _checker = bakeChecker({
        __optional: {
            a: ""
        },
        b: false,
        c: 0
    });
}

class Bar {
    my_string?: string;
    another_string: string;
    foo?: Foo;

    public static _checker = bakeChecker({
        __optional: {
            my_string: "",
            foo: Foo._checker
        },
        another_string: ""
    });
}

um den Typ zur Laufzeit zu prüfen:

if (checkType<Bar>(foreign_object, Bar._checker)) { ... }

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