创建通用 class 参数的实例

Create instance of generic class parameter

我想在 Typescript 的通用 class 中创建 T 的实例。 以下代码是我认为可以工作的,但不幸的是它没有。

type Constructor<T> = { new(...args: any[]): T };
class Bar { }
class Foo<T extends Constructor<T>> {

    constructor( /* some parameters here, but NOT a type creator! */ ) {}

    create(): T {
        return new T();
    }
}
const bar: Bar = new Foo<Bar>().create();

SO 上还有其他一些 questions/answers,但它们都使用某种需要传递给通用 class/function 的类型创建器,如下所示:

function create<T>(creator: new() => T): T {
    return new creator();
}
const bar: Bar = create<Bar>(Bar);

但我的 ctor 不需要有那样的东西。 create 函数应该始终保持无参数。有没有办法让这个工作?

简而言之:不,不引用可构造的运行时值是不可能的。

原因是 TypeScript 类型只存在于编译时(不存在于运行时),不能用作值。在您的示例中, T 只是一种类型(不是运行时值)。这是您的 TypeScript 程序编译将产生的 JavaScript:

class Bar {}

class Foo {
  constructor() { }
  create() {
    return new T();
  }
}

const bar = new Foo().create();
//                    ^^^^^^^^
// Exception is thrown: ReferenceError: T is not defined

您可以看到 T 是对不存在的运行时值的引用,因此在调用 create 方法时会抛出 ReferenceError 异常。


相反,您可以接受可构造对象(及其参数)和 return 构造实例:

TS Playground

type Constructible<
  Params extends readonly any[] = any[],
  T = any,
> = new (...params: Params) => T;

class Foo {
  static create <T extends Constructible>(
    constructible: T,
    ...params: ConstructorParameters<T>
  ): InstanceType<T> {
    return new constructible(...params);
  }
}

class Bar {}

class Baz {
  constructor (readonly param1: string, readonly param2: number) {}
}

const bar = Foo.create(Bar);
const baz = Foo.create(Baz, '2', 2);

如果没有对该 class 构造函数的运行时引用,则无法创建 class 的任何实例。

您可以将要创建的 class 作为值传递给创建者的构造函数,然后通常捕获该类型的值。

我会让你 Constructor non-generic,只是将其用作约束,让 typescript 推断其余部分。

type Constructor = { new (...args: any[]): any };

例如:

class Foo<T extends Constructor> {
    constructor(private ctor: T) {}

    create(): InstanceType<T> {
        return new this.ctor();
    }
}

这让你可以这样做:

class Bar { }

const foo = new Foo(Bar)
const bar1: Bar = foo.create(); // no parameters
const bar2: Bar = foo.create(); // no parameters

Playground


为了加分,您可以选择构造函数参数并让您的 create() 方法接受它们:

    create(...ctorArgs: ConstructorParameters<T>): InstanceType<T> {
        return new this.ctor(...ctorArgs);
    }

让我们来做:

class Bar {
    constructor(a: number, b: string) {}
}

const foo = new Foo(Bar)
const bar1: Bar = foo.create(1, 'a'); // no parameters
const bar2: Bar = foo.create(2, 'b'); // no parameters

Playground

或者您可以将其放入 creators 构造函数中?

class Foo<T extends Constructor> {
    ctorArgs: ConstructorParameters<T>

    constructor(private ctor: T, ...ctorArgs: ConstructorParameters<T>) {
        this.ctorArgs = ctorArgs
    }

    create(): InstanceType<T> {
        return new this.ctor(...this.ctorArgs);
    }
}

class Bar {
    constructor(a: number, b: string) {}
}

const foo = new Foo(Bar, 1, 'a')
const bar1: Bar = foo.create(); // no parameters
const bar2: Bar = foo.create(); // no parameters

Playground