TypeScript:通过提供类主体来创建通用匿名类

时间:2018-07-27 21:58:24

标签: typescript class generics typescript-generics typescript-class

标题可能会引起误解,但我真的不知道该如何表达我的问题。

我想提供一个泛型类(或只是类主体)作为函数参数。然后,所提供的类应推断其泛型。

class Builder<EXT, INT = EXT> {
    // some builder stuff, which changes the generic T
    withBar(): Builder<EXT, INT & { bar: string }> {
      return this as any;
    }

    // here's the problem:
    build(clazz: MyClass<INT>): MyClass<EXT> {
       return wrap(clazz); // this method already exists and works
    }
}

用法:

const builder = new Builder<{ foo: number }>();
// EXT = { foo: number }, INT = { foo: number }

builder = builder.withBar();
// EXT = { foo: number }, INT = { foo: number, bar: string }

builder.build(class { /* here I should be able to access this.foo and this.bar */ });
// Here I just want to provide the class body,
// because I don't want to type all the generics again.
// I don't want to specify `MyClass<?>`,
// because the correct type is already specified within the Builder

作为(丑陋的)“变通办法”,我找到了一种单独提供所有类方法,然后从中构建类的方法。像这样:

class Builder<EXT, INT = EXT> {
    build(args: {method1?: any, method2?: any}): MyClass<EXT> {
       class Tmp {
         method1() {
           return args.method1 && args.method1(this);
         }
         method2(i: number) {
           return args.method2 && args.method2(this);
         }
       }

       return wrap(Tmp);
    }
}

但这真的很丑。

基本上,我真的只想为build方法提供类主体。然后此方法将根据它创建一个类,调用wrap并返回。

有什么办法吗?

编辑:另一种尝试来解释我的问题:

此刻,我必须使用如下代码:

builder.build(class extends AbstractClass<{ foo: number, bar: string }> {
    private prop: string;
    init() {
      this.prop = this.data.foo   // provided by AbstractClass
          ? 'foo'
          : 'bar'
    }
    getResult() {
      return {
        foo: this.prop,
        bar: this.data.bar  // provided by AbstractClass
      }
    }
})

如您所见,我必须指定AbstractClass的泛型。我不想指定类型,因为builder已经知道类型。

我只想提供类的主体,而无需再次指定泛型类型。像这样:

builder.build(class extends AbstractClass<infer the type with magic!> {
    ...
    getResult() {
        return { this.data.foo }
    }
})

或者这个:

builder.build(class {
    ...
    getResult() {
        return { this.data.foo }
    }
})

2 个答案:

答案 0 :(得分:2)

我不是100%肯定我了解您想要什么,或者您想要什么是可行的。但是这里有一些想法...

首先,您所说的MyClass<T>似乎是指返回T的类构造函数。这种类型可以通过如下的构造函数签名来表示:

type Constructor<T> = new (...args: any[]) => T;

所以也许Builder应该是:

class Builder<EXT, INT = EXT> {
  // some builder stuff, which changes the generic T
  withBar(): Builder<EXT, INT & { bar: string }> {
    return this as any;
  }

  // here's maybe a solution:
  build(clazz: Constructor<INT>): Constructor<EXT> {
    return wrap(clazz) as any; // as any?  not sure
  }
}

然后,当您调用它时...首先,您无法在TypeScript中更改值的类型,因此您无法重新分配builder。但是您仍然可以进行链接(或为中间值指定新名称)。我将其链接。在这里:

const builder = new Builder<{ foo: number }>();

const ctor = builder.withBar().build(class {
  foo = 10;
  bar = "you";
});

这可行。但是请注意,您确实需要在匿名类主体中定义foobar。如果您不键入TypeScript,将会感到不高兴。因此,当您“拥有”访问权限时,可能并没有您想要的方便?


无论如何,如果还不够,请添加更多详细信息,也许我可以提供更多帮助。祝你好运!

答案 1 :(得分:0)

我只是有一个好主意。这会添加一些未使用的代码,但会执行类型推断。

这个想法很简单:只需使用typeof

class Builder<EXT, INT = EXT> {
    // some builder stuff, which changes the generic T
    withBar(): Builder<EXT, INT & { bar: string }> {
      return this as any;
    }

    build(clazz: (type: INT) => Constructor<INT>): MyClass<EXT> {
      return wrap(clazz(null as any) as any) as any;
    }
}

interface Constructor<T> {
    new (data: T): any;
}

现在可以像这样使用type: INT

// here happens the "magic"
builder.build((type) => class extends AbstractClass<typeof type> {
    getResult() {
        return { this.data.foo }
    }
})

我不确定是否有更好的解决方案。