Reputation: 397
I am currently using factory pattern in my project, below is the minimal reproducible code
class A {
constructor(public name: string) {}
}
class B {
constructor(public age: number, public address: string) {}
}
type FactoryMap = {
A: A,
B: B
}
const map = {
A,
B
}
function create<T extends keyof FactoryMap>(name: T, ...params: ConstructorParameters<typeof FactoryMap[T]>): FactoryMap[T] {
switch (name) {
case "A":
return new A(...params);
case "B":
return new B(...params);
}
}
function create2<T extends keyof typeof map>(name: T, ...params: ConstructorParameters<typeof map[T]>): typeof map[T] {
return new map[name](...params);
}
I've tried two different functions, but neither could work.
The first one said Type 'A' is not assignable to type 'A & B'
, but I wonder FactoryMap[T]
should be A | B
.
The second one's error is Type 'A | B' is not assignable to type '{ A: typeof A; B: typeof B; }[T]'
, I know why but I can't infer A | B
using the object map
.
Does anyone has solutions on those two functions?
Upvotes: 3
Views: 2106
Reputation: 250346
Typescript will not be able to evaluate conditional types that contain unresolved type parameters. You could solve this with overloads:
class A {
private name!: string
constructor() {}
}
class B {
private age!: number
constructor() {}
}
const map = { A, B }
function create2<T extends keyof typeof map>(name: T, ...params: ConstructorParameters<typeof map[T]>): InstanceType<typeof map[T]>
function create2(name: keyof typeof map, ...params: ConstructorParameters<typeof map[keyof typeof map]>): InstanceType<typeof map[keyof typeof map]> {
return new map[name](...params);
}
I added InstanceType
on the return type. typeof map[keyof typeof map]
will be the union typeof A | typeof B
(ie either of the classes). Since we return an instance, we need to use InstanceType
to get that.
The version above will not actually work if you have parameters to the constructor. This is because Typescript won't be able to follow that params
and map[name]
are in any way correlated. So TS sees new (A | B)(...(parmsForA | paramsForB)
which is only sometimes safe (if A
is paired with paramsForB
this would be an error)
Now since we know that the two are actually correlated, this is a good use of a type assertion:
class A {
constructor(public name: string) {}
}
class B {
constructor(public age: number, public address: string) {}
}
const map = { A, B }
function create2<T extends keyof typeof map>(name: T, ...params: ConstructorParameters<typeof map[T]>): InstanceType<typeof map[T]>
function create2(name: keyof typeof map, ...params: any[]): InstanceType<typeof map[keyof typeof map]> {
return new (map[name] as any)(...params)
}
Upvotes: 2