映射打字稿类型

Mapping typescript types

我正在尝试向函数添加类型,该函数将类型对象数组作为参数,return 另一种类型的映射数组:

const createAnimals = <T extends AnimalFactory<any>[]>(factories: T) => {
  return factories.map((f) => f());
};

一旦这个函数被正确输入,它应该会抛出一个错误,代码如下:

const factories = [() => new Dog(), () => new Cat()];
const animals = createAnimals(factories);

// ❌ should fail !
animals[0].meow();

我希望编译器知道 animals[Dog, Cat] 类型。

我一直在尝试使用 infer,但到目前为止没有成功。

可以这样做,但解决方案将要求您将 factories 键入为 read-only,否则该类型将根本不包含足够的信息。

const factories = [() => new Dog(), () => new Cat()] as const;

如果你为这样的工厂声明一个类型(这不再是真正关于动物的,但让我们坚持下去)

type AnimalFactory<T> = () => T

您可以创建一个映射类型,它采用 AnimalFactory 的元组并生成相应动物类型的元组:

type FactoryAnimals<T extends AnimalFactory<unknown>[]> =
  {[K in keyof T]: T[K] extends AnimalFactory<infer A> ? A : never}

现在您可以将 AnimalFactory 用于 createAnimals 的 return 类型:

const createAnimals = <T extends AnimalFactory<unknown>[]>
  (factories: readonly [...T]) => factories.map(f => f()) as FactoryAnimals<T>;

不幸的是,as FactoryAnimals<T> 断言是必要的,因为 TypeScript 无法推断在元组上进行映射会产生映射类型。 readonly [...T] 位是为了避免 read-only T,这会导致 read-only return 类型并需要额外的断言。

如果您在工厂上调用 createAnimals,它会生成所需类型的元组:

const animals = createAnimals(factories);
// type: [Dog, Cat]

// ❌ should fail !
animals[0].meow(); // Error: Property 'meow' does not exist on type 'Dog'.

如果你用显式数组而不是变量调用它,你可以省略 as const,因为 [...T] 类型导致 factories 被认为是一个元组:

const animals = createAnimals([() => new Dog(), () => new Cat()]);
// type: [Dog, Cat]

TypeScript playground