javascript - Variables de clase en Typescript y error: "se refiere a un valor, pero se está usando como un tipo aquí. ¿Quiso decir typeof ...". ¿Por qué exactamente?
Esto funciona en Javascript y Typescript:
class A { /* ... */ }
const B = class extends A { /* ... */ }
var x = new B();
console.log(x instanceof B, x.constructor.name); // true B
Pero si intento declarar el tipo de x como B:
var x: B = new B();
Recibo error de Typescript:
'B' se refiere a un valor, pero se está usando como un tipo aquí. ¿Quisiste decir 'typeof B'?
(Nota, también obtengo el mismo error si reemplazo const B=class extends A { /* ... */ } simplemente por const B=A, que es lo que tenía originalmente para hacer las cosas lo más simples posible, pero actualizado en la revisión).
Realmente no entiendo por qué debería ser así. Según el Javascript en tiempo de ejecución, x es un B (como se muestra en el console.log arriba). Y todas las clases son objetos ("valores") bajo el capó de todos modos (los propios documentos de Typescript equiparan las clases a "objetos constructor"). Supongo que es solo una limitación del análisis estático de Typescript: no puede determinar que "B" es un objeto constructor igual que "A" y seguir su tipado?
Y además, no lo creo, pero me pregunto, ¿hay realmente una manera de que esto funcione en Typescript: saber que B es un objeto constructor como A y permitir el uso de B como tipo??
P.D. Me doy cuenta de que hay muchas preguntas en SO relacionadas con "Blah se refiere a un valor, pero se está usando como un tipo aquí. ¿Quisiste decir 'typeof Blah'?", pero no pude encontrar preguntas y respuestas que lo abordaran tan directamente como arriba. Disculpas si me lo perdí.
Respuesta #1
Cuando escribes declaraciones class como
class A { a = 1 }
En TypeScript, estás llevando al alcance dos cosas diferentes que ambas se llaman A. Una es el valor llamado A, un constructor de clase que existe en tiempo de ejecución. La otra es el tipo llamado A, una interfaz de TypeScript que corresponde a instancias de la clase; tales tipos no existen en tiempo de ejecución.
También tenga en cuenta que un valor tiene un tipo, pero no es en sí mismo un tipo. Y el tipo del valor A no es A, ya que el constructor de la clase no es en sí mismo una instancia de la clase. En cambio, tiene el tipo typeof A (expresado usando el operador de consulta de tipo typeof de TypeScript), que es similar al tipo new() => A (expresado usando una firma de construcción).
El hecho de que el valor y el tipo se llamen ambos A es conveniente pero confuso. Es conveniente porque te permite usar un único término A para referirte a cosas relacionadas pero distintas sin necesidad de inventar nueva terminología para cada una (por ejemplo, A para el constructor de la clase, pero InstanceA para el tipo de instancia). Pero es confuso porque puede dar la falsa impresión de que estas dos cosas son realmente una sola, o que la relación entre estas dos cosas es de alguna manera inherente al nombre, cuando en realidad es solo incidental.
Por otro lado, cuando escribes declaraciones de variables como
const B = class extends A { b = 2 };
solo estás incorporando en el alcance un valor llamado B. No hay un tipo correspondiente llamado B. Si quieres tal tipo, tendrás que declararlo tú mismo:
type B = InstanceType<typeof B>;
La falta de un tipo automático llamado B no se debe a que el compilador no pueda determinar que B es un constructor de clase. El compilador sabe exactamente qué es B:
// const B: typeof B
Y sabe que construye instancias de B:
type WhatBConstructs = InstanceType<typeof B>;
// type WhatBConstructs = B // this "B" is not an actual type name, btw
(Aquí he usado el tipo de utilidad InstanceType<T> para sondear el tipo del constructor B para su tipo de instancia.)
Es solo que las declaraciones de variables nombran nuevos valores pero no nuevos tipos.
Existe una sugerencia en microsoft/TypeScript#36348 que solicita que, cuando vinculas un constructor de clase a una variable, también se cree un tipo nombrado correspondiente al tipo de la instancia. Si esto se implementara, entonces tu tipo B aparecería cuando escribieras const B=class extends A {...}. Si quieres ver que esto ocurra, podrías ir allí y darle un 👍, pero no esperaría ver cambios en ese tema en el futuro previsible.
De todos modos, por ahora, si creas tanto un valor como un tipo llamado B manualmente:
const B = class extends A { b = 2 };
type B = InstanceType<typeof B>;
entonces las cosas comenzarán a funcionar para ti de la manera que deseas:
var x: B = new B(); // okay
Enlace de Playground al código
