将数组映射到接口

Map array to an interface

假设我有一个如下所示的数组:

const options =  [
 {
   name: 'foo',
   type: 'boolean'
 },

 {
   name: 'bar',
   type: 'string'
 },

 {
   name: 'bar', // should be baz not bar
   type: 'number'
 }

]

我希望将此数组用作接口,它看起来像这样:

export interface Opts {
   foo: boolean,
   bar: string,
   baz: number
}

所以可能必须是这样的:

export type Opts = manipulate(typeof options);

where manipulate 是我希望发现的一些神奇的 TS 功能。

我相信这是一个很好的起点: https://blog.mariusschulz.com/2017/01/20/typescript-2-1-mapped-types

但很难弄清楚。

是的,您可以这样做,但它需要两种 mapped and conditional 类型。

首先,您需要一个类型来表示从 "boolean" 等类型名称到 boolean 等实际类型的映射。

type TypeMapping = {
  boolean: boolean,
  string: string,
  number: number,
  // any other types
}

然后您需要一个辅助函数来确保您的 options 值不会将 nametype 属性的类型扩展为 string。 (如果你检查你的 options 值,它的类型类似于 {name: string, type: string}[],它已经忘记了你想要的特定 nametype 值。)你可以使用 generic constraints来做这个,如下:

const asOptions = <K extends keyof any, 
  T extends Array<{ name: K, type: keyof TypeMapping }>>(t: T) => t;

让我们看看它是否有效:

const options = asOptions([
  {
    name: 'foo',
    type: 'boolean'
  },

  {
    name: 'bar',
    type: 'string'
  },

  {
    name: 'bar',
    type: 'number'
  }
]);

如果您检查一下,您会发现它现在是一个类型数组,其中每个 nametype 都缩小为文字 "foo""bar" , "number", 等等

最后我们必须执行您想要的 manipulate 类型的功能。我将其命名为 OptionsToType:

type OptionsToType<T extends Array<{ name: keyof any, type: keyof TypeMapping }>>
  = { [K in T[number]['name']]: TypeMapping[Extract<T[number], { name: K }>['type']] }

这可能看起来很复杂。让我们看看我是否可以分解它。

T extends Array<{ name: keyof any, type: keyof TypeMapping }>

表示T必须是一个对象数组,其中name字段像对象键,type字段像上面TypeMapping类型的键.

  = { [K in T[number]['name']]: ... }

T 数组

的每个元素遍历 name 属性 中的所有键名
  Extract<T[number], { name: K }>

表示"find the element of T that corresponds to the name K"...

  Extract<T[number], { name: K }>['type']

...并查找其 'type' 属性...

  TypeMapping[Extract<T[number], { name: K }>['type']]

...并将其用作 TypeMapping 类型的索引。

好的,让我们看看它是否有效:

export type Opts = OptionsToType<typeof options>;

如果你检查 Opts 你会看到:

{
    foo: boolean;
    bar: string | number;
}

正如你所料---呃,等等,为什么 bar 属性 类型是 string | number?哦,因为你把 bar 放在 options 中两次。将第二个更改为 baz,这将是您所期望的。

好的,希望对您有所帮助。祝你好运!