问题
I'm using mixins/traits with TypeScript using a subclass factory pattern as described at https://mariusschulz.com/blog/mixin-classes-in-typescript. The trait in question is called Identifiable, which imparts an id property to a class that should express the Identifiable trait. When I attempt to use the trait with another, non-generic trait (Nameable) in a certain order, compilation fails.
class Empty {}
type ctor<T = Empty> = new(...args: any[]) => T;
function Nameable<T extends ctor = ctor<Empty>>(superclass: T = Empty as T) {
return class extends superclass {
public name?: string;
};
}
function Identifiable<ID, T extends ctor = ctor<Empty>>(superclass: T = Empty as T) {
return class extends superclass {
public id?: ID;
};
}
class Person1 extends Nameable(Identifiable<string>()) { // compiles
constructor(name?: string) {
super();
this.name = name;
this.id = "none";
}
}
class Person2 extends Identifiable<string>(Nameable()) { // fails to compile
constructor(name?: string) {
super();
this.name = name;
this.id = "none";
}
}
Compilation error is
src/test/unit/single.ts:30:10 - error TS2339: Property 'name' does not exist on type 'Person2'.
30 this.name = name;
~~~~
How do I get generic traits to compile correctly, regardless of the order in which they're used?
NB: public git repo for this question is at https://github.com/matthewadams/typetrait. If you want to play with this, make sure to checkout the minimal branch.
回答1:
The issue is very simple actually, and it is related to the fact that typescript does not have partial type argument inference. The call Identifiable<string>(...) does not mean you set ID and let the compiler infer T. It actually means use string for ID and use the default (ie Empty) for T. This is unfortunate and there is a proposal to allow partial inference but it hasn't gained much traction.
You have two options, either use function currying to do a two call approach, where the first call passes ID and the second call infers T:
class Empty { }
type ctor<T = Empty> = new (...args: any[]) => T;
function Nameable<T extends ctor = ctor<Empty>>(superclass: T = Empty as T) {
return class extends superclass {
public name?: string;
};
}
function Identifiable<ID>() {
return function <T extends ctor = ctor<Empty>>(superclass: T = Empty as T) {
return class extends superclass {
public id?: ID;
};
}
}
class Person2 extends Identifiable<string>()(Nameable()) {
constructor(name?: string) {
super();
this.name = name;
this.id = "none";
}
}
Playground link
Or use inference on ID as well by using a dummy parameter as an inference site:
class Empty { }
type ctor<T = Empty> = new (...args: any[]) => T;
function Nameable<T extends ctor = ctor<Empty>>(superclass: T = Empty as T) {
return class extends superclass {
public name?: string;
};
}
function Identifiable<ID, T extends ctor = ctor<Empty>>(type: ID, superclass: T = Empty as T) {
return class extends superclass {
public id?: ID;
};
}
}
class Person2 extends Identifiable(null! as string, Nameable()) {
constructor(name?: string) {
super();
this.name = name;
this.id = "none";
}
}
Playground link
来源:https://stackoverflow.com/questions/57347557/why-does-this-typescript-mixin-employing-generics-fail-to-compile