【问题标题】:TypeScript: Create generic anonymous class by providing class bodyTypeScript:通过提供类体创建通用匿名类
【发布时间】:2019-01-05 01:20:02
【问题描述】:

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

我想提供一个泛型类(或只是类体)作为函数参数。然后提供的类应该推断出它的泛型。

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 }
    }
})

【问题讨论】:

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


    【解决方案1】:

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

    首先,您调用的MyClass&lt;T&gt; 似乎是指返回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 会很不高兴。因此,尽管您确实“可以访问”它们,但它可能并不像您想要的那么方便?


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

    【讨论】:

    • 谢谢!!我猜这很接近。现在的问题是clazz 必须扩展另一个类,它提供泛型...builder.build(class extends AbstractClass&lt;T&gt; { ... })。而这个T 就是问题所在。我不想写extends AbstractClass&lt;T&gt;,因为我必须再次指定泛型类型。还有什么想法吗? :)
    • 通常我必须使用build(class extends AbstractClass&lt;{ foo: number, bar: string}&gt; { ... })。但是Builder 已经知道关于泛型的一切。虽然我想以某种方式摆脱extends AbstractClass&lt;{ foo: number, bar: string}&gt;...EDIT... 也许这可以使用 mixins 来完成?
    • AbstractClass&lt;T&gt;的定义是什么?应该是AbstractClass&lt;T&gt; 扩展T 吗?还是别的什么?
    • 它不扩展TT 用作类属性和方法的参数。就像这样:class AbstractClass&lt;T&gt; { protected data: T; constructor(data: T) {this.data = data}; abstract init(): void; abstract getResult(): MyResult }.... 我想使用Builder 提供以下内容:builder.build(class { private prop: string; init() { this.prop = this.data.foo ? 'foo' : 'bar' } getResult() { return { foo: this.prop, bar: this.data.bar } } })。 (无用的例子,但你应该看看我想如何以及访问哪些属性)。顺便说一句:我会在 2 天内在这里添加一个大赏金:)
    【解决方案2】:

    我刚刚想到了一个好主意。这增加了一些未使用的代码,但实现了类型推断。

    这个想法很简单:只需使用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 }
        }
    })
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-07
      • 2018-12-18
      相关资源
      最近更新 更多