Monday, May 20, 2024
 Popular · Latest · Hot · Upcoming
53
rated 0 times [  57] [ 4]  / answers: 1 / hits: 5518  / 3 Years ago, wed, may 26, 2021, 12:00:00

This works in Javascript and Typescript:


class A  { /* ... */ }
const B = class extends A { /* ... */ }
var x = new B();
console.log(x instanceof B, x.constructor.name); // true B

But if I try to declare the type of x as B:


var x: B = new B();

I get Typescript Error:



'B' refers to a value, but is being used as a type here. Did you mean 'typeof B'?



(Note, I also get the same error if I replace const B = class extends A { /* ... */ } with simply const B = A, which is what I had originally to make things as simple as possible, but updated on review).


I don't really understand why this should be the case. According to runtime Javascript x is a B (as shown in the console.log above). And all classes are objects ("values") under the hood anyway (the Typescript docs themselves equate classes to "constuctor objects"). I'm guessing it's just a limitation of Typescript static analysis - it can't figure out that "B" is a constructor object just like "A" is and track down its typing?


And also, I don't think so, but wondering, is there actually a way to have this work in Typescript - know B is a constructor object like A and allow the use of B as a type??




P.S. I realize there are many questions on SO relating to "Blah refers to a value, but is being used as a type here. Did you mean 'typeof Blah'?", but I couldn't find Q&A approaching it as directly as above. Apologies if I missed it.


More From » typescript

 Answers
7

When you write class declarations like


class A { a = 1 }

in TypeScript, you are bringing into scope two different things which are both named A. One is the value named A, a class constructor that exists at runtime. The other is the type named A, a TypeScript interface corresponding to instances of the class; such types don't exist at runtime.


Also note that a value has a type, but it is not itself a type. And the type of the A value is not A, since the class constructor is not itself an instance of the class). Instead, it has the type typeof A (expressed using TypeScript's typeof type query operator), which is similar to the type new () => A (expressed using a construct signature).


The fact that the value and type are both named A is convenient but confusing. It is convenient because it lets you use the one term A to refer to related but distinct things without requiring you to invent new terminology for each (e.g., A for the class constructor, but InstanceA for the instance type). But it is confusing because it can give the false impression that these two things are really one thing, or that the relationship between these two things is somehow inherent in the name, when it's really just incidental.




On the other hand, when you write variable declarations like


const B = class extends A { b = 2 };

you are only bringing into scope a value named B. There is no corresponding type named B. If you want such a type, you will have to declare it yourself:


type B = InstanceType<typeof B>; 

The lack of an automatic type named B isn't because the compiler can't figure out that B is a class constructor. The compiler knows exactly what B is:


// const B: typeof B

And it knows that it constructs B instances:


type WhatBConstructs = InstanceType<typeof B>;
// type WhatBConstructs = B // this "B" is not an actual type name, btw

(Here I've used the InstanceType<T> utility type to probe the type of the B constructor for its instance type.)


It's just that variable declarations name new values but not new types.


There is an existing suggestion at microsoft/TypeScript#36348 which asks that when you bind a class constructor to a variable it should also make a named type corresponding to the instance type. If this were to be implemented, then your B type would appear when you wrote const B = class extends A {...}. If you want to see this happen, you could go there and give it a 👍, but I wouldn't expect to see any changes there for the foreseeable future.




Anyway, for now, if you create both a value and a type named B manually:


const B = class extends A { b = 2 };
type B = InstanceType<typeof B>;

then things will start working for you the way you want:


var x: B = new B(); // okay

Playground link to code


[#1312] Wednesday, May 19, 2021, 3 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
longd

Total Points: 616
Total Questions: 110
Total Answers: 101

Location: Andorra
Member since Sat, May 27, 2023
1 Year ago
longd questions
;