I want to create an instance of T
in a generic class in Typescript. The following code is something I thought would work, but unfortunately it does not.
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();
Well there are some other questions/answers here on SO, but all use some kind of type creator that needs to be passed to the generic class/function, like so:
function create<T>(creator: new() => T): T {
return new creator();
}
const bar: Bar = create<Bar>(Bar);
But my ctor needs to not have something like that. The create
function should always stay parameter-less. Is there a way to make this work?
In short: No it's not possible without a reference to a constructible runtime value.
The reason is that TypeScript types only exist at compile time (not at runtime) and can't be used as values. In your example, T
is only a type (not a runtime value). This is the JavaScript that your TypeScript program compilation would produce:
class Bar {} class Foo { constructor() { } create() { return new T(); } } const bar = new Foo().create(); // ^^^^^^^^ // Exception is thrown: ReferenceError: T is not defined
You can see that T
is a reference to a runtime value that doesn't exist, and so it throws a ReferenceError
exception when the create
method is invoked.
Instead, you can accept a constructible object (and its arguments) and return a constructed instance:
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);
You can't create any instance of a class without a runtime reference to that class constructor.
You could pass your class to be created as a value to the creator's constructor, and then generically capture that type's value.
I would make you Constructor
non-generic, just to use it as a constraint, and let typescript infer the rest.
type Constructor = { new (...args: any[]): any };
For example:
class Foo<T extends Constructor> {
constructor(private ctor: T) {}
create(): InstanceType<T> {
return new this.ctor();
}
}
Which lets you do this:
class Bar { }
const foo = new Foo(Bar)
const bar1: Bar = foo.create(); // no parameters
const bar2: Bar = foo.create(); // no parameters
For bonus points, you can pick up the constructor parameters and make your create()
method take them:
create(...ctorArgs: ConstructorParameters<T>): InstanceType<T> {
return new this.ctor(...ctorArgs);
}
Which let's you do:
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
Or you could put this in the creators constructor instead?
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
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.