通过通用函数动态修改TypeScript类

时间:2017-05-18 12:32:46

标签: javascript class typescript factory

如何通过泛型函数在类上创建静态方法?

假设您要在一组类上添加函数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类本身)的构造函数是shouldMakemake属性有效的类型。

你会怎么做?基本上,如何让类类型从我们可以约束它的接口继承,或者,如何在类中描述有效的静态属性?根据{{​​3}}和https://github.com/Microsoft/TypeScript/issues/13462似乎不支持这种情况,因此根据此逻辑的大型JavaScript代码库无法转换为TypeScript(即排除或多或少完全重写)?

1 个答案:

答案 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
}