januw a
januw a

Reputation: 2248

typescript instantiation type definition

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

Answers (2)

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

Playground

More about iteration through tuple type you can find in my blog

Upvotes: 1

januw a
januw a

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

Related Questions