如何通过泛型函数在类上创建静态方法?
假设您要在一组类上添加函数make
,具体取决于该类是静态shouldMake
是true还是false。这些make
函数应该是工厂,创建类的实例。
以下JavaScript工作原理:
function makeMaker( cls )
{
if ( cls.shouldMake )
cls.make = function( ...args ) { return new cls( ...args ); };
return cls;
}
您可以通过以下方式运行一组类:
outClasses = inClasses.map( makeMaker );
我希望这样的东西能在TypeScript中运行:
function makeMaker< T >( cls: T ): T
{
if ( cls.shouldMake )
cls.make = function( ...args: any[] ) { return new cls( ...args ); }
return cls;
}
但是,这会导致一些错误:
属性'shouldMake'在'T'类型上不存在。
和
属性'make'在'T'类型上不存在。
和
不能对类型缺少调用或构造签名的表达式使用“new”。
在这种情况下,cls
会推断出类型为T 的值。如果你提供一个类作为参数(cls
)作为某种“类的类型”,我期望T,例如构造函数。将其更改为构造函数:
function makeMaker< T >( cls: { new( ...args: any[] ): T } )
{
if ( cls.shouldMake )
cls.make = function( ...args: any[] ) { return new cls( ...args ); };
return cls;
}
导致:
属性'shouldMake'在'new(... args:any [])=&gt;类型上不存在T”。
和
属性'make'在类型'new(... args:any [])=&gt;上不存在T”。
这有点道理,尽管即使从未调用makeMaker
也会出现此错误。因此,我们需要确保T(T类本身)的构造函数是shouldMake
和make
属性有效的类型。
你会怎么做?基本上,如何让类类型从我们可以约束它的接口继承,或者,如何在类中描述有效的静态属性?根据{{3}}和https://github.com/Microsoft/TypeScript/issues/13462似乎不支持这种情况,因此根据此逻辑的大型JavaScript代码库无法转换为TypeScript(即排除或多或少完全重写)?
答案 0 :(得分:2)
这听起来像可能是方法和类型转换的用例。在我找到实际的解决方案之前,我将介绍必要的前言。首先,我们可以声明为所有可以成为制造者的类类型实现的接口(使用T
类型的对象的构造函数):
interface Class<T> {
shouldMake: boolean;
new(... args: any[]): T;
}
然后我们为所有可以成为制造者的对象定义一个接口(他们创建类型为T
的对象)。方法是否可用取决于字段shouldMake
。
interface Maker<T> {
shouldMake: boolean;
make?(...args: any[]): T;
}
现在我们可以实施makeMaker
。如果我们将类对象强制转换为C & Maker<T>
,那么make
就可以存在,我们可以正常设置它。方法原型的重要部分是确保C
具有构造函数和shouldMake
字段,并且返回类型保留有关C
的所有信息,以及Maker
中的额外方法{1}}。
function makeMaker<T, C extends Class<T>>( cls: C ): C & Maker<T>
{
if (cls.shouldMake)
(<C & Maker<T>>cls).make = function( ...args: any[] ) { return new cls( ...args ); };
return cls;
}
生成的JavaScript与您在开头时所拥有的相同:
function makeMaker(cls) {
if (cls.shouldMake)
cls.make = function (...args) { return new cls(...args); };
return cls;
}
以下是使用示例。请注意,您应该始终检查make
是否可用,因为编译器无法为您验证。
class Foo {
private name?: string;
static shouldMake = true;
new(name?: string) {
this.name = name;
}
hasName(): boolean {
return typeof this.name === 'string' && this.name !== '';
}
}
const FooMaker = makeMaker<Foo, typeof Foo>(Foo);
if (FooMaker.shouldMake) {
let unnamedFoo = FooMaker.make();
console.log(unnamedFoo.hasName()); // false
} else {
// unreachable in this case
}