I have a superObject()
function which just combined a lot of objects:
type Class<T = any> = {
new(...args: any[]): T
}
function superObject<C extends Class[]>(...classes: C) {
let obj = {};
for(const klass of classes) {
obj = {
...obj,
...new klass()
}
}
return obj;
}
There does not seem to be a good way to annotate the return type, as obj
will have every property of every class. I have tried marking the function as returning C[number]
, but this gives a union (classes[0] | classes[1]
, not classes[0] & classes[1]
). How would I get this type at compile time?
CodePudding user response:
If you define a recursive type:
type MergeTypes<T extends unknown[]> =
T extends [a: infer A, ...rest: infer R] ? A & MergeTypes<R> : {};
to merge all of the types in a tuple T
, then you can:
type Class<T = any> = {
new(...args: any[]): T
}
function superObject<C extends Class[]>(...classes: C): MergeTypes<C> {
let obj: Partial<MergeTypes<C>> = {};
for (const klass of classes) {
obj = {
...obj,
...new klass()
}
}
return obj as MergeTypes<C>;
}
REVISION
Upon using the code above, I realize that it's merging the constructor function types, not the types that are constructed. Here's a revised version that extracts the T
from Class<T>
and uses that instead.
type Class<T = any> = {
new(...args: any[]): T
}
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
type MergeTypes<T extends Class[]> =
T extends [a: Class<infer A>, ...rest: infer R]
? R extends Class[]
? A & MergeTypes<R>
: never
: {};
function superObject<C extends Class[]>(...classes: C): Expand<MergeTypes<C>> {
let obj: Partial<MergeTypes<C>> = {};
for (const klass of classes) {
obj = {
...obj,
...new klass()
}
}
return obj as Expand<MergeTypes<C>>;
}
class A {
public foo = 1
}
class B {
public bar = "monkey"
}
class C {
public yum = true;
}
const so = superObject(A, B, C)