Por que 'instanceof' no TypeScript me dá o erro “'Foo' apenas se refere a um tipo, mas está sendo usado como um valor aqui.”?

93

Eu escrevi este código

interface Foo {
    abcdef: number;
}

let x: Foo | string;

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

Mas o TypeScript me deu este erro:

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

Por que isso está acontecendo? Achei que isso instanceofpoderia verificar se meu valor tem um determinado tipo, mas o TypeScript parece não gostar disso.

Daniel Rosenwasser
fonte
Veja a resposta abaixo @ 4castle. Caso contrário, você está certo, eu farei isso Foo | string.
Daniel Rosenwasser
1
Possível duplicata de verificação de tipo
texto de
E possível duplicata de Verificar se a variável é um tipo de interface específico em uma união datilografada (eu realmente não quero martelar isso sozinho)
Cerbrus
@Jenny O'Reilly, essa é definitivamente uma duplicata de uma Possível duplicata!
marckassay

Respostas:

105

O que está acontecendo

O problema é que instanceof é uma construção do JavaScript e, em JavaScript, instanceofespera um valor para o operando do lado direito. Especificamente, no x instanceof FooJavaScript irá executar uma verificação de tempo de execução para ver se Foo.prototypeexiste em qualquer lugar na cadeia de protótipos de x.

No entanto, no TypeScript, interfaces não têm emit. Isso significa que Foonem Foo.prototypeexiste nem em tempo de execução, portanto, este código irá falhar definitivamente.

O TypeScript está tentando dizer que isso nunca funcionaria. Fooé apenas um tipo, não é um valor!

"O que posso fazer em vez de instanceof?"

Você pode examinar os protetores de tipo e os protetores de tipo definidos pelo usuário .

"Mas e se eu apenas mudasse de um interface para um class?"

Você pode ficar tentado a mudar de um interfacepara um class, mas você deve perceber que no sistema de tipo estrutural do TypeScript (onde as coisas são principalmente baseadas na forma ), você pode produzir qualquer objeto que tenha a mesma forma de uma determinada classe:

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;

Nesse caso, você tem xe yque tem o mesmo tipo, mas se tentar usar instanceofem um deles, obterá o resultado oposto no outro. Então, instanceofnão vai realmente dizer muito sobre o tipo se você está aproveitando tipos estruturais à máquina.

Daniel Rosenwasser
fonte
2
Teria levado séculos para descobrir isso por mim mesma!
Matthew Layton
Então, basicamente, não entendi qual seria a melhor resposta. Classe? porque você detalhou. Mas confuso ao mesmo tempo que mencionou "talvez você tenha tentado". E daí se eu tiver que comparar todas as propriedades e não apenas a propriedade nadar como nos documentos para protetores de tipo?
HalfWebDev
8
O ponto principal aqui é que instanceoffunciona com classes, não interfaces. Pensei que isso precisava ser enfatizado.
inorganik
5

Fazer a verificação de tipo em tempo de execução com uma interface é usar protetores de tipo , se as interfaces que você deseja verificar têm propriedades / funções diferentes .

Exemplo

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
fonte
E se eu aprender sobre patos e adicionar a função swim () à minha interface Bird? Todo animal de estimação não seria classificado como peixe em um tipo de guarda? E se eu tiver três interfaces com três funções cada e duas sobrepostas com uma das outras interfaces?
Kayz
1
@Kayz se você não tem propriedades / funções que identificam exclusivamente uma interface, você não pode realmente diferenciá-los. Seu animal de estimação pode ser na verdade um Duck, você digita guarda, ele se torna Fish, mas ainda sem exceção de tempo de execução quando você invoca swim(). Sugiro que você crie 1 nível de interface comum (por exemplo Swimmable) e mova suas swim()funções para lá, então digite guard ainda ficará bem com ((pet as Swimmable).swim.
Lee Chee Kiam
Para evitar o typecasting, você pode usar a 'swim' in petcondição. Ele vai reduzi-lo a um subconjunto que tem de ter swimdefinido (ex: Fish | Mammal)
Akxe
2

Daniel Rosenwasser pode estar certo e elegante, mas estou com vontade de fazer uma alteração à sua resposta. É totalmente possível verificar a instância de x, consulte o trecho de código.

Mas é igualmente fácil atribuir x = y. Agora, x não seria uma instância de C, pois y só tinha a forma 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
fonte