Reputation: 1587
Why doesn't this compile?
interface ITest { ... }
class Test implements ITest { ... }
class Factory<T extends ITest> {
constructor(private _Test: typeof Test) { }
create(): T {
return new this._Test();
}
}
It gives Type 'Test' is not assignable to type 'T'. (property) Factory<T extends ITest>._Test: new () => Test
.
How I could make it work is by either
create(): ITest
or through
private _Test: any
Both would not really be communicating (code-wise) what I am after. Any way in which I could make it work?
Upvotes: 1
Views: 3511
Reputation: 19222
Why doesn't this compile?
It doesn't compile because the type of _Test
is typeof Test
. That can be confusing so let me try to break that down:
Typescript add "types" to the world of javascript giving you a way to say "I expect this object to have all these properties and operations available to them.". In an essence, this is all a type system does.
Typescript has the typeof
operator which acts like a type query as a way to get the "type" from an instantiated object when used on the right side of a type annotation (the :
). The typeof
gives you a way to say "whatever properties and operations that this object has, I want to capture those and reuse them to check the type of another object".
Here is an example of using this typeof
operator.
let myObject = {
a: 'a string',
b: 5,
c: false
};
// notice that the `typeof` operator is on the right of the `:`
function takesATypeOfMyObject(obj: typeof myObject) {
// highlight over `a`, `b`, or `c` to see their type
obj.a // (property) a: string
obj.b // (property) b: number
obj.c // (property) c: boolean
}
myObject
is a normal javascript object literal. The function takesATypeOfMyObject
is a function with one parameter obj
with the type annotation of typeof myObject
. This function is saying that is can take in any object with the same properties as the object myObject
.
This is not to be confused with javascript's typeof
operator that only returns a string. Typescript's typeof
operator is part of it's type system. Remember that when typescript is compiled to javascript, the types and type annotations go away.
Another important thing to understand is how classes in typescript work. Classes are kind of a doubled-edged-sword. In typescript, classes act as both:
function
you can call with new
to get the mentioned typeIt might be tempting to using the type annotation typeof MyClass
but this is most likely not what you intended.
Consider the example below:
// define a class Animal
// this creates the type definition of `Animal` as well as
// the constructor to create an `Animal`
class Animal {
makeNosie() { return 'grrr'; }
}
// use the constructor to create an object of type `Animal`
let myAnimal = new Animal();
// now `myAnimal` has an inferred type of `Animal`
// declare an object to of type `Animal` but instead of using
// the `Animal` constructor, just define an object literal that
// conforms to the type of Animal
let dog: Animal = {
makeNosie: () => 'woof'
};
// the type of dog has been declared explicitly and typescript
// just checks to see if it conforms to the type
So hopefully you see that if you want an object to conform to the type that a class creates, you don't use the typeof
operator, you just say "Animal", not typeof Animal
.
But that brings us to the question: what if you did that?
Remember that the typeof
operator tries to capture the type to be reused. Since class
es define both a type and a new
able function to create an object of that type, what typeof Animal
is actually doing is querying the type of the constructor of Animal.
Okay so now we can finally dig in your code to understand why it isn't compiling. Below is the original code you pasted:
interface ITest { } class Test implements ITest { } class Factory<T extends ITest> { constructor(private _Test: typeof Test) { } create(): T { return new this._Test(); } }
Take a look at the constructor
of Factory
. What you're saying with this code is that "The constructor
of my class Factory
takes any Object that conforms to the constructor of Test. That's one pretty complicated type (and probably not what you intended).
Instead try this code:
interface ITest { }
class Test implements ITest { }
class Factory<T extends ITest> {
constructor(private _Test: new() => T) { }
create(): T {
return new this._Test();
}
}
The type description of _Test
has been changed to new() => T
which is saying that "the constructor of Factory takes in any new
able function that returns a the type T
".
Hopefully that's what you've intended.
I hope I've help you solved your problem as well as show you how powerful typescript is. Typescript is trying to do a very ambitious and crazy thing: add full types to any possible javascript. I think they're doing a great job.
Upvotes: 6