是否可以将某些键的值用作打字稿中的新键?

is it possible to use value of some key as new key in typescript?

假设我有一个对象列表,每个对象都是 IUser 类型

type IUser = {
 id: string
 name: string
 ...
}

我想将此列表转换为地图,使用 id 作为键(或来自对象的其他 属性)。

希望我能有一个准确的类型描述,这样我就可以像这样使用它const idMap :IMapKeyedByK<IUser, 'id'> = {...}

type IMapKeyedByK<T, K> = {
  [key: T[K]]: T
}

如何实现 IMapKeyedByK

请注意,JavaScript 对象索引类型始终是字符串或符号。您可以使用数字索引对象,但它们会转换为字符串:({42: 'foo'})['42'] == 'foo'。因此,除非您的 IUser 包含 symbols,否则这不是很有用,因为您最好对 string 键进行硬编码。

也就是说,你可以做到。我们必须将 K 约束为有效的键类型,并约束 T 以便它包含类型 K 的键,其值可以 用作键。

type ObjectKey = string | number | symbol
type IMapKeyedByK<T extends Record<K, ObjectKey>, K extends ObjectKey> = Record<T[K], T>

const idMap: IMapKeyedByK<IUser, 'id'> = {}
idMap['zaphod'] = {id: 'zaphod', name: 'Zaphod Beeblebrox'} // OK
idMap[Symbol()] = {id: 'zaphod', name: 'Zaphod Beeblebrox'} // Error! Type 'symbol' cannot be used as an index type.

这里的正确类型是:

type IMapKeyedByK<T, K extends keyof T> = T[K] extends PropertyKey // if the property value can be used as key
    ? Record<T[K], T>  // record indexed by the property value
    : never;   

它将确保您传递属于 T 的密钥,并且该密钥的值可有效用作对象密钥。

示例:

type IUser = {
 id: string
 name: string
 age: number
 department: {
     departmentName: string
 }
}

type IMapKeyedByK<T, K extends keyof T> = T[K] extends PropertyKey ? Record<T[K], T> : never;

type MapById   = IMapKeyedByK<IUser, "id">;   //works - indexed by string
type MapByName = IMapKeyedByK<IUser, "name">; //works - indexed by string
type MapByAge  = IMapKeyedByK<IUser, "age">;  //works - indexed by number

type MapByDepartment = IMapKeyedByK<IUser, "department">; //produces `never` because cannot be indexed by object
type MapByPhone = IMapKeyedByK<IUser, "phone">;           //error - "phone" does not exist

Playground Link