Reputation: 487
I have a class
, containing a static
and a non-static
parameter:
class Test {
s1 = "Hello"
static s2 = 16
}
I then create 2 keyof types, one using typeof and one without:
type t1 = typeof Test[keyof typeof Test]
type t2 = Test[keyof Test]
Type t1
is a union
between Test | number
while type t2
is string
.
What is the actual difference between the two? Does typeof return the types of static methods + the class? Why? What is the reasoning?
Upvotes: 1
Views: 980
Reputation: 7176
tl;dr
typeof Test
as a type refers to the type of the value of Test
(a class function).Test
as a type refers to the type of an instance of Test
(an object that looks like this: { s1: 'hello' }
).typeof
typeof
can do one of two things in TypeScript, depending on the context. From the handbook:
JavaScript already has a
typeof
operator you can use in an expression context:// Prints "string" console.log(typeof "Hello world");
TypeScript adds a
typeof
operator you can use in a type context to refer to the type of a variable or property:let s = "hello"; let n: typeof s; // ^ let n: string
The situation you have described is using the second typeof
operator (existing in the type context).
TypeScript classes are a bit strange; every other declaration in TypeScript will result in a new type or a new object (almost, TypeScript enums are an exception). For example, you can't use a variable a type, nor a type as a variable:
const foo = 'bar';
const baz: foo = foo;
// ^^^ 'foo' refers to a value, but is being used as a type here.
// Did you mean 'typeof foo'?
interface fizz {
buzz: string
}
const fizzbuzz = fizz['buzz'];
// ^^^^ 'fizz' only refers to a type,
// but is being used as a value here.
Declaring a class is different because you end up with both a TypeScript type and a JavaScript class, where the type represents an instance of the class. In other words, you can do this:
class Foo {}
// As a JavaScript constructor:
const foo = new Foo();
// As a TypeScript type:
let bar: Foo;
// As both:
const baz: Foo = new Foo();
One more thing to note: in JavaScript, classes are just functions, and functions are just objects. Adding a static
property to a TypeScript class compiles to look something like this in JavaScript:
// TypeScript
// class Foo { static bar = 'baz' }
// compiles to:
class Foo {
}
Foo.bar = 'baz';
typeof Class
Regardless of whether you use typeof
in the expression context or the type context, you must always use it on a value type. In other words, you can't do:
type Abc = 'hi';
type Zyx = typeof Abc;
// ^^^
// 'Abc' only refers to a type, but is being used as a value here.
Because of this, when we know that typeof Test
is going to use the value of Test
rather than its type; as you noted, this is different from the type, Test
. So, what is the type of typeof Test
?
Remember, JavaScript classes are just functions that can be used as constructors (try running console.log(typeof Test)
for proof). typeof Class
is going to give you a type that represents that constructor function with additional narrowing specific to that class. You could use it like so:
class Test {
s1 = "Hello"
static s2 = 16
}
type T = typeof Test;
const Foo: T = class Bar extends Test { }
// Because of duck typing, you can also do this:
class Test2 {
s1 = "Hello"
static s2 = 32
}
const Fizz: T = class Buzz extends Test2 { }
In other words, Test
as a type gives you the structure of an instance, while typeof Test
as a type gives you the structure of the class itself.
Looking at the keyof
behavior:
// The keys of an instance of test:
type Keys = keyof Test; // 's1'
// The keys of the Test class, which is just a function and so includes
// the inherited 'prototype' property:
type Keys2 = keyof typeof Test; // 'prototype' | 's2'
And finally:
type t1 = typeof Test[keyof typeof Test];
// -> typeof Test['prototype' | 's2'] -> Test | number
type t2 = Test[keyof Test]
// -> Test['s1'] -> string
Upvotes: 3