I am trying to find the best approach for narrowing the return type of a factory without passing in the class itself. Below is an example using conditional types, which is the best approach I've found so far. Is there a cleaner way to narrow the return type?
Setup
class Lion {
run() {
// Do something
}
}
class Fish {
swim() {
// Do something
}
}
class Snake {
slither() {
// Do something
}
}
enum AnimalType {
LION = 'lion',
FISH = 'fish',
SNAKE = 'snake',
}
This example returns a union type, which I don't want
class AnimalFactory {
public static create(type: AnimalType) {
switch (type) {
case AnimalType.LION:
return new Lion();
case AnimalType.FISH:
return new Fish();
case AnimalType.SNAKE:
return new Snake();
default:
throw new Error('Invalid Type');
}
}
}
const fish = AnimalFactory.create(AnimalType.FISH); // Type: Lion | Fish | Snake :(
This example returns the correct type, but seems like a lot of overhead
type Animal<T> = T extends AnimalType.LION
? Lion
: T extends AnimalType.FISH
? Fish
: T extends AnimalType.SNAKE
? Snake
: unknown;
class AnimalFactory {
public static create<T extends AnimalType>(type: T): Animal<T> {
switch (type) {
case AnimalType.LION:
return new Lion() as Animal<T>;
case AnimalType.FISH:
return new Fish() as Animal<T>;
case AnimalType.SNAKE:
return new Snake() as Animal<T>;
default:
throw new Error('Invalid Type');
}
}
}
const fish = AnimalFactory.create(AnimalType.FISH); // Type: Fish :)
Is there a cleaner way to narrow the factory return type using only the AnimalType
argument?
You need a type that describes which class type is returned for which enum value:
interface AnimalFactoryReturnType {
[AnimalType.LION]: Lion;
[AnimalType.FISH]: Fish;
[AnimalType.SNAKE]: Snake;
}
Then you can use keyof
to select it out:
class AnimalFactory {
public static create<T extends keyof AnimalFactoryReturnType>(type: T): AnimalFactoryReturnType[T] {
switch (type) {
case AnimalType.LION:
return new Lion();
case AnimalType.FISH:
return new Fish();
case AnimalType.SNAKE:
return new Snake();
default:
throw new Error('Invalid Type');
}
}
}
const fish = AnimalFactory.create(AnimalType.FISH); // Type: Fish :)