Проверка типа интерфейса с помощью Typescript

этот вопрос является прямым analogon к проверка типа класса с помощью 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"}; без жалоб от редактора. После просмотра веб-страниц и поиска другого вопроса я изменил интерфейс на класс, но затем я не могу использовать объектные литералы для создания экземпляров.

Я задавался вопросом, как тип A может исчезнуть, но взгляд на сгенерированный javascript объясняет проблему:

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

нет представления a как интерфейса, поэтому проверки типа среды выполнения невозможны.

Я понимаю, что JavaScript как динамический язык не имеет понятия интерфейсов. Есть ли способ ввести check для интерфейсов?

автозаполнение TypeScript playground показывает, что typescript даже предлагает метод implements. Как я могу его использовать ?

9 ответов


вы можете достичь того, чего вы хотите, без 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);
}

В TypeScript 1.6, определяемый пользователем тип guard сделает работу.

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

и так же, как Джо Янг упомянул: начиная с 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 тоже.


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

Как насчет пользовательских охранников типа? 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();
}

теперь это возможно, я только что выпустил расширенную версию 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 мета-тип детали.

обновление: Вы можете найти полный рабочий пример здесь


Я хотел бы отметить, что TypeScript не предоставляет прямого механизма для динамического тестирования, реализует ли объект определенный интерфейс.

вместо этого код TypeScript может использовать метод JavaScript для проверки наличия соответствующего набора элементов в объекте. Например:

var obj : any = new Foo();

if (obj.someInterfaceMethod) {
    ...
}

TypeGuards

interface MyInterfaced {
    x: number
}

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

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

вот еще один вариант: модуль 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); }

то же, что и выше, где пользовательские охранников были использованы, но на этот раз с предикатом функции стрелки

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