Let's create a solution that eliminates the need to manually input generics for each class.
Initially, you can simplify this code:
export interface Type<T> extends Function {
new (...args: any[]): T;
}
to:
type Constructor<T> = new (...args: any[]) => T
To minimize repetitive code, utilize variadic tuple types.
In order to deduce the constructor of each class, apply them in this manner:
function mynew<T extends Constructor<any>, Classes extends T[]>(...klasses: [...Classes]) {
return klasses.map((e) => new e());
}
const result = mynew(ClassA, ClassB)
If you hover over mynew(ClassA, ClassB)
, you'll notice that the second generic argument Classes
is inferred as [typeof ClassA, typeof ClassB]
.
Now, let's iterate through each element in the tuple and acquire [InstanceType][2]
This is how it can be achieved:
type MapPredicate<T> = T extends Constructor<any> ? InstanceType<T> : never
type Mapped<
Arr extends Array<unknown>,
Result extends Array<unknown> = []
> = Arr extends []
? []
: Arr extends [infer H]
? [...Result, MapPredicate<H>]
: Arr extends [infer Head, ...infer Tail]
? Mapped<[...Tail], [...Result, MapPredicate<Head>]>
: Readonly<Result>;
// [ClassA, ClassB]
type Result = Mapped<[typeof ClassA, typeof ClassB]>
Remember, there exists a significant difference between the type typeof ClassA
and ClassA
. The former refers to the constructor, while the latter indicates the class instance.
Let’s combine everything together:
type Constructor<T> = new (...args: any[]) => T
class ClassA { x = 1 }
class ClassB { y = 1 }
class ClassC { z = 1 }
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
type MapPredicate<T> = T extends Constructor<any> ? InstanceType<T> : never
type Mapped<
Arr extends Array<unknown>,
Result extends Array<unknown> = []
> = Arr extends []
? []
: Arr extends [infer H]
? [...Result, MapPredicate<H>]
: Arr extends [infer Head, ...infer Tail]
? Mapped<[...Tail], [...Result, MapPredicate<Head>]>
: Readonly<Result>;
// [ClassA, ClassB]
type Result = Mapped<[typeof ClassA, typeof ClassB]>
function mynew<T extends Constructor<any>, Classes extends T[]>(...klasses: [...Classes]): Mapped<Classes> & unknown[]
function mynew<T extends Constructor<any>, Classes extends T[]>(...klasses: [...Classes]) {
return klasses.map((e) => new e());
}
const result = mynew(ClassA, ClassB, ClassC)
const [a, b, c] = result;
a.x // ok
b.y // ok
c.z // ok
Playground
For more information on iterating through tuple types, refer to my blog