Pourquoi 'instanceof' dans TypeScript me donne-t-il l'erreur «'Foo' ne fait référence qu'à un type, mais est utilisé comme valeur ici.»?

91

J'ai écrit ce code

interface Foo {
    abcdef: number;
}

let x: Foo | string;

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

Mais TypeScript m'a donné cette erreur:

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

Pourquoi cela arrive-t-il? Je pensais que cela instanceofpourrait vérifier si ma valeur avait un type donné, mais TypeScript ne semble pas aimer cela.

Daniel Rosenwasser
la source
Voir la réponse ci-dessous @ 4castle. Sinon, tu as raison, je vais y arriver Foo | string.
Daniel Rosenwasser
1
Copie
Et possible duplication de Vérifier si la variable est un type d'interface spécifique dans une union dactylographiée (je ne veux pas vraiment marteler cela à lui seul)
Cerbrus
@Jenny O'Reilly, maintenant c'est définitivement un double d'un éventuel doublon!
marckassay

Réponses:

100

Que se passe-t-il

Le problème est qu'il instanceofs'agit d'une construction de JavaScript, et en JavaScript, instanceofattend une valeur pour l'opérande de droite. Plus précisément, en x instanceof FooJavaScript effectuera une vérification d'exécution pour voir s'il Foo.prototypeexiste n'importe où dans la chaîne de prototypes de x.

Cependant, dans TypeScript, les interfaces n'ont pas d'émission. Cela signifie que ni Fooni Foo.prototypen'existe au moment de l'exécution, donc ce code échouera définitivement.

TypeScript essaie de vous dire que cela ne pourrait jamais fonctionner. Fooest juste un type, ce n'est pas du tout une valeur!

"Que puis-je faire à la place de instanceof?"

Vous pouvez examiner les protections de type et les protections de type définies par l'utilisateur .

"Mais que faire si je passais juste d'un interface à un class?"

Vous pourriez être tenté de passer de an interfaceà a class, mais vous devez comprendre que dans le système de type structurel de TypeScript (où les choses sont principalement basées sur la forme ), vous pouvez produire n'importe quel objet qui a la même forme qu'une classe donnée:

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;

Dans ce cas, vous avez xet yqui ont le même type, mais si vous essayez d'utiliser instanceofl'un ou l'autre, vous obtiendrez le résultat opposé sur l'autre. Donc instanceof, ne vous en dira pas vraiment beaucoup sur le type si vous tirez parti des types structurels de TypeScript.

Daniel Rosenwasser
la source
2
Cela m'aurait pris des années pour le repérer moi-même!
Matthew Layton
Donc, fondamentalement, je n'ai pas eu l'idée de la réponse qui est préférable. Classe? parce que vous l'avez détaillé. Mais confus en même temps que vous avez mentionné "vous avez peut-être tenté". Alors que faire si je dois comparer toutes les propriétés et pas seulement nager la propriété comme dans la documentation pour les gardes de type?
HalfWebDev
5
Le point principal ici est que cela instanceoffonctionne avec des classes, pas des interfaces. Pensée qu'il fallait souligner.
inorganik
5

Pour effectuer la vérification de type lors de l'exécution avec une interface, vous utilisez des protections de type , si les interfaces que vous souhaitez vérifier ont des propriétés / fonctions différentes.

Exemple

let pet = getSmallPet();

if ((pet as Fish).swim) {
    (pet as Fish).swim();
} else if ((pet as Bird).fly) {
    (pet as Bird).fly();
}
Lee Chee Kiam
la source
Et si j'apprends les canards et que j'ajoute la fonction swim () à mon interface Bird? Est-ce que chaque animal de compagnie ne serait pas classé comme poisson dans un type de garde? Et si j'ai trois interfaces avec trois fonctions chacune et deux se chevauchent avec l'une des autres interfaces?
Kayz le
1
@Kayz si vous n'avez pas de propriétés / fonctions qui identifient de manière unique une interface, vous ne pouvez pas vraiment les différencier. Votre animal de compagnie qui pourrait être en fait un Duck, vous tapez guard il devient Fish, mais toujours pas d'exception d'exécution lorsque vous appelez swim(). Suggérez-vous de créer 1 niveau d'interface commune (par exemple Swimmable) et de déplacer vos swim()fonctions là-bas, puis le type guard semble toujours bon avec ((pet as Swimmable).swim.
Lee Chee Kiam
Pour éviter la conversion de type, vous pouvez utiliser 'swim' in petcondition. Il réduire à un sous - ensemble qui doit avoir swimdéfini (ex: Fish | Mammal)
Akxe
2

Daniel Rosenwasser a peut-être raison et dandy, mais j'ai envie de modifier sa réponse. Il est tout à fait possible de vérifier l'instance de x, voir l'extrait de code.

Mais il est également facile d'attribuer x = y. Maintenant, x ne serait pas une instance de C car y n'avait que la forme de 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
Elias Vesterlund
la source