我正在使用https://mariusschulz.com/blog/mixin-classes-in-typescript中所述的通过子类工厂模式将Mixins / traits与TypeScript一起使用。有问题的特征称为Identifiable
,它为应该表达id
特征的类赋予Identifiable
属性。当我尝试按特定顺序将特征与另一个非通用特征(Nameable
)一起使用时,编译会失败。
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";
}
}
编译错误为
src/test/unit/single.ts:30:10 - error TS2339: Property 'name' does not exist on type 'Person2'.
30 this.name = name;
~~~~
无论如何使用通用特征,我如何都能使其正确编译?
NB:此问题的公共git回购位于https://github.com/matthewadams/typetrait。如果您想玩这个游戏,请确保签出minimal
分支。
答案 0 :(得分:4)
这个问题实际上很简单,这与打字稿没有部分类型实参推论的事实有关。调用Identifiable<string>(...)
并不意味着您设置了ID
并让编译器推断T
。实际上,这意味着对string
使用ID
,对Empty
使用默认值(即T
)。这很不幸,虽然有proposal to allow partial inference,但并没有获得太大的吸引力。
您有两个选择,可以使用函数currying进行两次调用,其中第一次调用通过ID
,第二次调用推断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";
}
}
或通过将虚拟参数用作推断位点来对ID
进行推断:
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";
}
}