Reputation: 2248
The function mynew
receives the class array and returns the instantiated array. How can I define unknown
to make the error disappear
export interface Type<T> extends Function {
new (...args: any[]): T;
}
function mynew(klass: Type<unknown>[]): unknown[] {
return klass.map((e) => new e());
}
class ClassA { x = 1 }
class ClassB { y = 1 }
const [a, b] = mynew([ClassA, ClassB]);
// The attribute "x" does not exist on the type "unknown"
a.x
// The attribute "y" does not exist on the type "unknown"
b.y;
Upvotes: 1
Views: 96
Reputation: 33041
Let's make a solution which does not require to manualy type each generic for each class.
First of all, you can simplify this code:
export interface Type<T> extends Function {
new (...args: any[]): T;
}
to:
type Constructor<T> = new (...args: any[]) => T
In order to reduce repetitive code, you can use variadic tuple types.
In order to infer each class constructor, you can use them in this way:
function mynew<T extends Constructor<any>, Classes extends T[]>(...klasses: [...Classes]) {
return klasses.map((e) => new e());
}
const result = mynew(ClassA, ClassB)
Please hover your mouse on mynew(ClassA, ClassB)
, you will se that second generic argument Classes
is infered to [typeof ClassA, typeof ClassB]
.
Now, we need to iterate through each element in tuple and obtain [InstanceType][2]
Here is how we can do it:
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]>
Please, keep in mind, there is a big difference between typeof ClassA
type and ClassA
. First refers to constructor, second to class instance.
Let's put it all together:
type Constructor<T> = new (...args: any[]) => T
class ClassA { x = 1 }
class ClassB { y = 1 }
class ClassC { z = 1 }
// credits goes to https://stackoverflow.com/a/50375286
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
More about iteration through tuple type you can find in my blog
Upvotes: 1
Reputation: 2248
This is my solution, is there a better solution
function mynew<A, B>(klass: [Type<A>, Type<B>]): [A, B];
function mynew<A>(klass: [Type<A>]): [A];
function mynew(klass: Type<any>[]): any {
return klass.map((e) => new e());
}
Upvotes: 0