如何使用基于对象值的键创建对象类型

时间:2019-11-29 12:21:28

标签: typescript

我正在尝试将对象转换为动态创建的类型。但是我在努力做到正确。

假设我有一个对象:

const constants = {
   filter: 'flr',
   color: 'col'
}

我将如何进行输入,这将允许以下操作:

type MetaData = {
   flr: number
   col: string
}

//js
const meta: MetaData = getMeta();
const filterValue: number = meta[constants.filter];

每当常量值之一更改时,我都希望类型也自动更新。

我尝试通过以下操作进行设置:

type MetaData = {
  [constants.filter]: number
  [k: constants.filter]: number
  [k: any extends constants.filter]: number
}

但是似乎没有任何效果,而且我似乎无法为此找到合适的解决方案。有任何有关工作类型的提示吗?

1 个答案:

答案 0 :(得分:2)

const constants = {
  filter: 'flr',
  color: 'col'
} as const; // very important

type Constants = typeof constants; // represents the type of the constants object
type ConstantsValues = Constants[keyof Constants] // defines values of constants object
type MetaData = {
   [K in ConstantsValues]: any // map which has all keys as values of constants object
}

// using
const meta: MetaData = {
  flr: 1,
  col: 2
}
const filterValue: number = meta[constants.filter];

说明

最重要的是将constants定义为const,这意味着我们说的是对象的这种结构是永久性的,并且推断出的类型应该精确地做到这一点。感谢consttypeof constants定义了对象拥有的确切键和值。

接下来的事情是ConstantsConstantsValues这两种类型,它们只是来自constants对象的类型的简单定义。它们是出于可读性目的。

最后一个是MetaData。我们将其定义为map,其中哪些键都是constants对象的值。我将值设置为any,因为您没有定义对此的需要。

constants对象的任何更改都会影响更改Metadata类型的每个实例的需要。


如果整个Metadata对象应该具有一种类型的值(如示例中的number所示),则可以通过以下方式实现:

type MetaData = {
   [K in ConstantsValues]: number // here we have a number for all values
}

或更复杂的方式:

type MetaData<V> = {
   [K in ConstantsValues]: V // here we have V for all values
}

type MetaDataStr = MetaData<string>
type MetaDataNum = MetaData<number>

评论中还要求其他需求。需要能够定义将用不同类型的值表示constants对象的一部分的类型。这是实现该示例的示例:

// bigger object to have an example
const constants = {
  filter: 'flr',
  color: 'col',
  rank: 'rnk',
  size: 'sz'
} as const;

// the same two types as previous
type Constants = typeof constants;
type ConstantsValues = Constants[keyof Constants];

// create parts of values 
type PartOfConstantsA = Extract<ConstantsValues, 'flr' | 'col'>
type PartOfConstantsB = Extract<ConstantsValues, 'rnk' | 'sz'>

// create more generic type in order to pass also keys by Keys generic
type MetaData<Keys extends PropertyKey, Values> = {
  [K in Keys]: Values
}

// we combine both types by &
type FinalMetaData = MetaData<PartOfConstantsA, number> & MetaData<PartOfConstantsB, string>;

// using
const meta: FinalMetaData = {
  flr: 1, // needs to be number
  col: 2,// needs to be number
  rnk: 'a', // needs to be string
  sz: 'b' // needs to be number
}
const filterValue = meta[constants.filter];