为什么这个使用泛型的 TypeScript mixin 编译失败?
Why does this TypeScript mixin employing generics fail to compile?
我将 mixins/traits 与 TypeScript 一起使用,使用子 class 工厂模式,如 https://mariusschulz.com/blog/mixin-classes-in-typescript 所述。所讨论的特征称为 Identifiable
,它将 id
属性 传递给 class,该 class 应该表达 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;
~~~~
无论使用顺序如何,如何使通用特征正确编译?
注意:public git 这个问题的回购在 https://github.com/matthewadams/typetrait。如果你想玩这个,一定要检查 minimal
分支。
这个问题其实很简单,和typescript没有部分类型参数推断有关。调用 Identifiable<string>(...)
并不意味着您设置 ID
并让编译器推断 T
。它实际上意味着对 ID
使用 string
,对 T
使用默认值(即 Empty
)。不幸的是,有一个 proposal to allow partial inference 但它没有获得太多关注。
您有两个选择,要么使用函数柯里化进行两次调用方法,其中第一个调用传递 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";
}
}
我将 mixins/traits 与 TypeScript 一起使用,使用子 class 工厂模式,如 https://mariusschulz.com/blog/mixin-classes-in-typescript 所述。所讨论的特征称为 Identifiable
,它将 id
属性 传递给 class,该 class 应该表达 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;
~~~~
无论使用顺序如何,如何使通用特征正确编译?
注意:public git 这个问题的回购在 https://github.com/matthewadams/typetrait。如果你想玩这个,一定要检查 minimal
分支。
这个问题其实很简单,和typescript没有部分类型参数推断有关。调用 Identifiable<string>(...)
并不意味着您设置 ID
并让编译器推断 T
。它实际上意味着对 ID
使用 string
,对 T
使用默认值(即 Empty
)。不幸的是,有一个 proposal to allow partial inference 但它没有获得太多关注。
您有两个选择,要么使用函数柯里化进行两次调用方法,其中第一个调用传递 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";
}
}