例如,将对象键映射到另一个键的函数。
function mapKeys<O extends Record<string, unknown>, K extends keyof O, KM extends Record<K, string>>(obj: O, keyMap: KM) {
const r: Record<KM[keyof KM], O[K]> = {}
for (const k of Object.keys(obj)) r[keyMap[k]] = obj[k]
return r
}
该函数将遇到错误: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Record<KM[keyof KM], any>'. No index signature with a parameter of type 'string' was found on type 'Record<KM[keyof KM], any>'.(7053)
据我所知,这是因为属性值类型keyMap
是string
而不是字符串文字。
const x = mapKeys({a: 1}, {a: 'b'} as const)
/*
'as const' instructs the compiler it's a literal,
so here the `x.b` will correctly inferred to be `string`.
*/
但是如何让编译器在声明类型时将对象属性值类型推断为字符串文字而不是字符串?(当源代码是 Javascript 时,不可能对as const
值使用断言)
PSkeyMap
不确定。(所以enum
或union
不起作用。)
使编译器推断在输出对象映射的键,关键是要缩小keyMap
与Partial<>
:
function mapKeys<O extends Record<string, unknown>,
K extends keyof O,
KM extends Partial/* narrow keyMap */<Record<K, string>>>(obj: O, keyMap: KM) {
const r: Record<KM[keyof KM], O[K]> = {}
for (const k of Object.keys(obj)) r[keyMap[k]] = obj[k]
return r
}
// here `x.b` will be inferred (as `a`'s type) without using `as const`
const x = mapKeys({a: 1}, {a: 'b'})
如果这就是您要找的,请告诉我:
// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
/**
* Get union of all values ov the object
*/
type Values<T> = T[keyof T]
/**
* Iterate through the Dictionary - in our case KeyMap
*
*/
type Rename<Obj, Dictionary> =
/**
* Check if Dictionary is a Map structure
*/
Dictionary extends Record<string, string>
/**
* Get all created key/values pair and merge them
* Hence, we only using newly created key/values pairs.
* Obj is not returned from this util type
*/
? UnionToIntersection<Values<{
[Prop in keyof Dictionary]: Prop extends keyof Obj
/**
* Create new key/value pair
*/
? Record<Dictionary[Prop], Obj[Prop]>
: never
}>>
: never
{
// { customAge: 42; }
type Test = Rename<{ age: 42 }, { age: 'customAge' }>
// unknown - I don't know how you want to handle it
type Test2 = Rename<{ age: 42 }, { notExists: 'customAge' }>
// never - because second argument is not a dictionary
type Test3 = Rename<{ age: 42 }, 42>
}
function mapKeys<
/**
* Apply constraint for all keys of Object
*/
ObjKeys extends PropertyKey,
/**
* Apply constraint for all values of Object
*/
ObjValues extends PropertyKey,
/**
* Infer Object with appropriate Keys and Values
*/
Obj extends Record<ObjKeys, ObjValues>,
/**
* Apply constraint for Key of KeyMap
*/
Keys extends keyof Obj,
/**
* Aplly constraint for KeyMap keys
*/
NewKeys extends PropertyKey,
/**
* Infer KeyMap, Keys should extend Object keys
*/
KeyMap extends Record<Keys, NewKeys>
>(obj: Obj, keyMap: KeyMap): Rename<Obj, KeyMap>
function mapKeys<
ObjKeys extends PropertyKey,
ObjValues extends PropertyKey,
Obj extends Record<ObjKeys, ObjValues>,
NewKeys extends PropertyKey,
KeyMap extends Record<keyof Obj, NewKeys>,
>(obj: Obj, keyMap: KeyMap) {
return (Object.keys(obj) as Array<keyof Obj>)
.reduce((acc, elem) => ({
...acc,
[keyMap[elem]]: obj[elem]
}), {} as Record<PropertyKey, Values<Obj>>)
}
const result = mapKeys({ age: 1 }, { "age": "newAge" })
result.newAge // 1
游乐场如果是 - 我会提供解释
请记住,r[keyMap[k]]
如果你想让它类型安全,突变不是最好的选择。TS 不跟踪突变。看我的文章
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句