I have the example below which describes a structure to pass to the Jumpable class. When i pass the Test class i get an error.
type GConstructor<T> = new (...args: any[]) => T;
type Positionable = GConstructor<{ setPos: (x: number, y: number) => void }>;
function Jumpable<TBase extends Positionable>(Base: TBase) {
return class Jumpable extends Base {
jump() {
this.setPos(0, 20);
}
};
}
class Test {
constructor() {
console.log();
}
setPos(x: number, y: number) {
console.log();
}
}
Jumpable<Test>(new Test());
Type 'Test' does not satisfy the constraint 'Positionable'. Type 'Test' provides no match for the signature 'new (...args: any[]): { setPos: (x: number, y: number) => void; }'.ts(2344)
CodePudding user response:
It's important to understand the difference between JavaScript values which exist at runtime, and TypeScript types which exist only at compile time.
A type is kind of like a set of possible values. In the following code:
let x = "hello";
the variable x
will hold the value "hello"
at runtime, while the TypeScript compiler infers that it has the type string
. And "hello"
is one of the possible values that make up the string
type, so that's fine. In the above, we could say "the type of x
is string
".
If you have a variable like x
, you can ask TypeScript what its type is, by using the TypeScript typeof
type query operator in a type context (not to be confused with the JavaScript typeof
operator):
type TypeofX = typeof x;
// type TypeofX = string
Values are not types and types are not values. Much of the time there is a clear and obvious difference between values and types in TypeScript, or the difference is minor enough to ignore. For example, "singleton" or "unit" types that correspond to exactly one value are often given the same name as the value, like the string literal type "hello"
corresponds to the value "hello"
, so you could get by just fine conflating one with the other.
But sometimes a value and a type can share a name but the value and type do not correspond directly to each other. The major situation where this happens is with class
declarations.
In the following:
class Foo {
x: string = "hello"
}
we have declared a class
named Foo
. At runtime, in JavaScript, this will create a value named Foo
which is a class constructor. The value named Foo
can be invoked as a constructor like new Foo()
. TypeScript also knows about this value.
Additionally, the class
declaration also brings into scope a TypeScirpt type named Foo
, which corresponds to instances of the class. A value of type Foo
should have an x
property of type string
.
And the type of the Foo
value is not the Foo
type. They are different. The type of the Foo
value, typeof Foo
, has a construct signature like new () => Foo
. But the type named Foo
has no such construct signature.
Observe:
const foo: Foo = new Foo();
const fooAlias: { x: string } = foo;
const fooCtor: typeof Foo = Foo;
const fooCtorAlias: new () => Foo = fooCtor;
const nope: Foo = Foo; // error!
// Property 'x' is missing in type 'typeof Foo' but required in type 'Foo'
const alsoNope: typeof Foo = new Foo(); // error!
// Property 'prototype' is missing in type 'Foo' but required in type 'typeof Foo'
The foo
and fooAlias
variables are of type Foo
or an equivalent type and hold an instance of the Foo
class. The fooCtor
and fooCtorAlias
variables are of type typeof Foo
or an equivalent type and hold the Foo
class constructor. If you try to assign one to the other, you get errors.
So, let's look at your code:
type GConstructor<T> = new (...args: any[]) => T;
type Positionable = GConstructor<{ setPos: (x: number, y: number) => void }>;
function Jumpable<TBase extends Positionable>(Base: TBase) {
return class Jumpable extends Base {
jump() {
this.setPos(0, 20);
}
};
}
Here, the Jumpable()
function wants a Base
parameter of type TBase
which is constrained to Positionable
, a type with a construct signature that constructs instances of type { setPos(x: number, y: number) => void }
. A Positionable
must be a class constructor. This all makes sense because Jumpable()
returns a new class constructor that is a subclass of the passed-in Base
. It looks like a mixin class factory.
Jumpable()
wants its input to be a class constructor.
When you call this, though:
Jumpable<Test>(new Test());
You have passed it a value of type Test
. Jumpable()
doesn't want a value whose type is Test
; that's a class instance, not a class constructor. You get the error:
Jumpable<Test>(new Test()); // error!
// Type 'Test' does not satisfy the constraint 'Positionable'.
// Type 'Test' provides no match for the signature
// 'new (...args: any[]): { setPos: (x: number, y: number) => void; }'
which tells you exactly the problem: you are trying to use the instance type Test
in a place that needs a constructor type. Test
is not a constructor; it has no construct signature; it's wrong. If you compile and run that code, you'll get a runtime error too, like
//