¿Por qué 'instanceof' en TypeScript me da el error "'Foo' solo se refiere a un tipo, pero aquí se usa como un valor"?

91

Escribí este código

interface Foo {
    abcdef: number;
}

let x: Foo | string;

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

Pero TypeScript me dio este error:

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

¿Por qué está pasando esto? Pensé que instanceofpodría comprobar si mi valor tiene un tipo determinado, pero parece que a TypeScript no le gusta esto.

Daniel Rosenwasser
fuente
Vea la respuesta a continuación @ 4castle. De lo contrario, tienes razón, lo lograré Foo | string.
Daniel Rosenwasser
1
Posible duplicado de la verificación del tipo
Typecript
Y posible duplicado de Comprobar si la variable es un tipo de interfaz específico en una unión mecanografiada (realmente no quiero martillar esto sin ayuda)
Cerbrus
@Jenny O'Reilly, ¡definitivamente es un duplicado de un posible duplicado!
Marckassay

Respuestas:

100

Que esta pasando

El problema es que instanceofes una construcción de JavaScript, y en JavaScript, instanceofespera un valor para el operando del lado derecho. Específicamente, en x instanceof FooJavaScript se realizará una verificación en tiempo de ejecución para ver si Foo.prototypeexiste en algún lugar de la cadena de prototipos de x.

Sin embargo, en TypeScript, los interfaces no tienen emisión. Eso significa que ni Fooni Foo.prototypeexisten en tiempo de ejecución, por lo que este código definitivamente fallará.

TypeScript está tratando de decirle que esto nunca podría funcionar. Fooes solo un tipo, ¡no es un valor en absoluto!

"¿Qué puedo hacer en lugar de instanceof?"

Puede buscar en los protectores de tipos y los protectores de tipos definidos por el usuario .

"¿Pero qué pasa si acabo de cambiar de una interface a una class?"

Es posible que tenga la tentación de cambiar de an interfacea a class, pero debe darse cuenta de que en el sistema de tipos estructurales de TypeScript (donde las cosas se basan principalmente en la forma ), puede producir cualquier objeto que tenga la misma forma que una clase determinada:

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;

En este caso, tiene xy ytiene el mismo tipo, pero si intenta usar instanceofen cualquiera de los dos, obtendrá el resultado opuesto en el otro. Por instanceoflo tanto , no le dirá mucho sobre el tipo si está aprovechando los tipos estructurales en TypeScript.

Daniel Rosenwasser
fuente
2
¡Me habría llevado siglos descubrirlo por mí mismo!
Matthew Layton
Así que, básicamente, no entendí la idea de la respuesta con cuál es mejor. ¿Clase? porque lo detallaste. Pero confundido al mismo tiempo que mencionaste "tal vez te sientas tentado". Entonces, ¿qué pasa si tengo que comparar todas las propiedades y no solo las propiedades de natación como en los documentos para guardias de tipo?
HalfWebDev
5
El punto principal aquí es que instanceoffunciona con clases, no con interfaces. Pensé que eso necesitaba ser enfatizado.
inorganik
5

Para realizar la verificación de tipos en tiempo de ejecución con una interfaz, se utilizan protectores de tipos , si las interfaces que desea verificar tienen propiedades / funciones diferentes .

Ejemplo

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
fuente
¿Qué pasa si aprendo sobre los patos y agrego la función swim () a mi interfaz Bird? ¿No se clasificarían todas las mascotas como peces en un protector de tipo? ¿Y si tengo tres interfaces con tres funciones cada una y dos se superponen con una de las otras interfaces?
Kayz
1
@Kayz si no tiene propiedades / funciones que identifiquen de manera única una interfaz, realmente no puede diferenciarlas. Tu mascota que podría ser en realidad un Duck, escribe guard se convierte Fish, pero aún no hay excepción de tiempo de ejecución cuando invocas swim(). Sugiero que cree 1 nivel de interfaz común (por ejemplo Swimmable) y mueva sus swim()funciones allí, luego type guard todavía se ve bien con ((pet as Swimmable).swim.
Lee Chee Kiam
Para evitar el encasillamiento puede utilizar 'swim' in petcondition. Será limitar la búsqueda a un subconjunto que tiene que haber swimdefinido (por ejemplo: Fish | Mammal)
Akxe
2

Daniel Rosenwasser puede tener razón y ser elegante, pero me apetece hacer una enmienda a su respuesta. Es completamente posible verificar la instancia de x, vea el fragmento de código.

Pero es igualmente fácil asignar x = y. Ahora, x no sería una instancia de C ya que y solo tenía la 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
fuente