0

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

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

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

2 回答 2

2

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

首先,您所调用的MyClass<T>似乎是指返回 a 的类构造函数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 中改变值的类型,所以你不能 reassign builder。但是你仍然可以做一个链(或给中间值新名称)。我会把它锁起来。这里:

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

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

所以这行得通。但请注意,您确实需要在匿名类主体内部定义foo和。bar如果你把它们排除在外,TypeScript 会很不高兴。因此,尽管您确实“可以访问”它们,但它可能不像您想要的那样方便?


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

于 2018-07-27T22:12:20.400 回答
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 }
    }
})

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

于 2018-07-29T14:05:50.893 回答