Почему instanceof в TypeScript выдает ошибку «Foo относится только к типу, но здесь используется как значение»?

93

Я написал этот код

interface Foo {
    abcdef: number;
}

let x: Foo | string;

if (x instanceof Foo) {
    // ...
}

Но TypeScript дал мне эту ошибку:

'Foo' only refers to a type, but is being used as a value here.

Почему это происходит? Я думал, что это instanceofможет проверить, имеет ли мое значение данный тип, но TypeScript, похоже, это не нравится.

Даниэль Розенвассер
источник
См. Ответ ниже @ 4castle. В противном случае ты прав, я справлюсь Foo | string.
Даниэль
1
Возможный дубликат проверки типа интерфейса с помощью
Typescript
И возможный дубликат проверки, является ли переменная определенным типом интерфейса в объединении машинописных текстов (я действительно не хочу забивать это в одиночку)
Cerbrus
@ Дженни О'Рейли, теперь это определенно дубликат Возможного дубликата!
marckassay

Ответы:

105

Что происходит

Проблема в том, что instanceofэто конструкция из JavaScript, а в JavaScript instanceofожидает значение для правого операнда. В частности, в x instanceof FooJavaScript выполняется проверка времени выполнения, чтобы увидеть, Foo.prototypeсуществует ли где-нибудь в цепочке прототипов x.

Однако в TypeScript у interfaces нет emit. Это означает, что во время выполнения Fooне Foo.prototypeсуществует и не существует, поэтому этот код определенно не сработает.

TypeScript пытается сказать вам, что это никогда не сработает. Fooэто просто тип, а вовсе не значение!

"Что я могу сделать вместо instanceof?"

Вы можете изучить защиты типов и защиты типов, определяемые пользователем .

"Но что, если я просто перейду с А interface на А class?"

У вас может возникнуть соблазн переключиться с a interfaceна a class, но вы должны понимать, что в системе структурных типов TypeScript (где вещи в основном основаны на форме ) вы можете создать любой объект, который имеет ту же форму, что и данный класс:

class C {
    a: number = 10;
    b: boolean = true;
    c: string = "hello";
}

let x = new C()
let y = {
    a: 10, b: true, c: "hello",
}

// Works!
x = y;
y = x;

В этом случае у вас есть xи, yкоторые имеют один и тот же тип, но если вы попытаетесь использовать instanceofодин из них, вы получите противоположный результат на другом. Так instanceofчто особо не расскажу вам о типе, если вы пользуетесь преимуществами структурных типов в TypeScript.

Даниэль Розенвассер
источник
2
Мне потребовалась бы целая вечность, чтобы заметить это для себя!
Мэтью Лейтон,
Так что в основном я не понял из ответа, что лучше пойти. Класс? потому что вы это подробно описали. Но запутался в то же время, когда вы упомянули «возможно, вас искушали». Так что, если мне нужно сравнить все свойства, а не только свойство swim, как в документации для type guard?
HalfWebDev
8
Главное здесь то, что instanceofработает с классами, а не интерфейсами. Мысль, которую нужно подчеркнуть.
inorganik
5

Для проверки типов во время выполнения с помощью интерфейса используется защиты типов , если интерфейсы, которые вы хотите проверить, имеют разные свойства / функции.

пример

let pet = getSmallPet();

if ((pet as Fish).swim) {
    (pet as Fish).swim();
} else if ((pet as Bird).fly) {
    (pet as Bird).fly();
}
Ли Чи Киам
источник
Что, если я узнаю об утках и добавлю функцию swim () в свой интерфейс Bird? Не будет ли каждое домашнее животное классифицироваться как рыба в типовой гвардии? А если у меня есть три интерфейса с тремя функциями в каждом и два перекрываются с одним из других интерфейсов?
Kayz
1
@Kayz, если у вас нет свойств / функций, которые однозначно идентифицируют интерфейс, вы не сможете их отличить. Ваш питомец, который может быть на самом деле a Duck, вы набираете guard, которым он становится Fish, но по-прежнему не вызывает исключения во время выполнения swim(). Предложите вам создать 1 уровень общего интерфейса (например Swimmable) и переместить swim()туда свои функции, тогда тип guard по-прежнему будет хорошо выглядеть ((pet as Swimmable).swim.
Lee Chee Kiam
Чтобы предотвратить приведение типов, вы можете использовать 'swim' in petусловие. Это будет сократить его до подмножества , который должен быть swimопределен (например: Fish | Mammal)
Akxe
2

Даниэль Розенвассер, может быть, прав и щеголь, но мне хочется внести поправку в его ответ. Вполне возможно проверить экземпляр x, см. Фрагмент кода.

Но так же легко присвоить x = y. Теперь x не был бы экземпляром C, поскольку y имел только форму C.

class C {
a: number = 10;
b: boolean = true;
c: string = "hello";
}

let x = new C()
let y = {
    a: 10, b: true, c: "hello",
}

console.log('x is C? ' + (x instanceof C)) // return true
console.log('y is C? ' + (y instanceof C)) // return false
Элиас Вестерлунд
источник