I am trying add types to a function, that takes as parameters an array of typed objects, and return a mapped array of another type :
const createAnimals = <T extends AnimalFactory<any>[]>(factories: T) => {
return factories.map((f) => f());
};
Once this function is properly typed, it should throw an error with the following code :
const factories = [() => new Dog(), () => new Cat()];
const animals = createAnimals(factories);
// ❌ should fail !
animals[0].meow();
I would like the compiler to know that animals
is of type [Dog, Cat]
.
I have been trying to use infer
, but without success so far.
CodePudding user response:
It's possible to do this, but the solution will require you to type factories
as read-only, or the type will simply not contain enough information.
const factories = [() => new Dog(), () => new Cat()] as const;
If you declare a type for such factories (which aren't really about animals anymore, but let's stick with it)
type AnimalFactory<T> = () => T
you can create a mapped type that takes a tuple of AnimalFactory
's and yields a tuple of the respective animal types:
type FactoryAnimals<T extends AnimalFactory<unknown>[]> =
{[K in keyof T]: T[K] extends AnimalFactory<infer A> ? A : never}
Now you can use AnimalFactory
for the return type of createAnimals
:
const createAnimals = <T extends AnimalFactory<unknown>[]>
(factories: readonly [...T]) => factories.map(f => f()) as FactoryAnimals<T>;
Unfortunately the as FactoryAnimals<T>
assertion is necessary since TypeScript can't infer that doing a map on the tuple yields the mapped type. The readonly [...T]
bit is to avoid having a read-only T
, which would cause a read-only return type and require an extra assertion.
If you call createAnimals
on the factories it produces a tuple of the desired type:
const animals = createAnimals(factories);
// type: [Dog, Cat]
// ❌ should fail !
animals[0].meow(); // Error: Property 'meow' does not exist on type 'Dog'.
And if you call it with an explicit array instead of a variable, you can leave out the as const
, since the [...T]
type causes factories
to be considered a tuple:
const animals = createAnimals([() => new Dog(), () => new Cat()]);
// type: [Dog, Cat]