<AK extends string, DK extends string>(map: Record<AK, DK>)
这不起作用,因为您丢失了键和值之间的映射。这意味着如果您有一个映射对象:
{ a: 'x', b: 'y' }
你最终会得到:
{ [K in 'a' | 'b']: 'x' | 'y' }
现在该类型无法再跟踪'a'
映射到'x'
和'b'
映射到 的类型'y'
。
您还有一个返回类型,Record<any, GV>
它没有任何帮助,因为键实际上是any
。
所以我们需要一个更好的输入泛型类型和一个更好的输出泛型类型。
要解决此问题,您需要对整个对象类型(键映射和数据)进行通用,而不仅仅是它们的键和值。
export function createMapper< const TKeyMap extends Record<string, string>,>
现在您可以通过 获取键keyof TKeyMap
或通过 获取值TKeyMap[keyof TKeyMap]
。或者,更重要的是,将类型映射为:
{ [K in keyof TKeyMap]: TKeyMap[K] } // identical to `TKeyMap`
此映射类型保留哪些键映射到哪些值。这个非常重要。
最后,const
泛型类型的前缀告诉 Typescript 推断文字类型,而不仅仅是string
. 这将推断TKeyMap
为 as{ a: 'x', b: 'y' }
而不是{ a: string, b: string }
。但你也可以as const
在传入的对象后面抛出,如下所示:
createMapper({ ... } as const)
现在可以更简单地输入内部函数参数和泛型:
return < TData extends Partial< Record<keyof TKeyMap, unknown> > >( objectToMap: TData ): // TODO: return type here
这表示该objectToMap
参数必须具有与 相同的键的子集TKeyMap
,每个键都有一个unknown
值类型。TData
将捕获任何东西,我们可以通过它。
内部函数的返回类型是有趣的地方。看起来像这样:
{ [ K in keyof TData as K extends keyof TKeyMap ? TKeyMap[K] : never ]: TData[K] }
这就是映射类型的用武之地。
它映射 的键TData
并根据 重命名TKeyMap
它们as
。
但我们需要确保它K
实际上位于TKeyMap
第一个,因为TData
可能是具有额外键的约束的子类型。如果密钥在那里,则查找将其映射为新密钥名称的内容。如果不存在,则该键解析为never
并从结果中省略该键。
对于值,只需使用TData
.key上的任何值类型K
即可TData[K]
。
对于函数体,可以使用以下方法来避免一些头痛:
const result = {} as any
然后进行您的实施。我几乎从不建议使用any
,但在像这样的高度通用的情况下,一个简单的函数具有复杂的输入/输出类型,很难让打字稿来允许你尝试做的事情。
因此,用一些测试来覆盖这个函数,并告诉 Typescript 你知道你在做什么。
把所有这些放在一起可能看起来像:
export function createMapper< const TKeyMap extends Record<string, string>,>(map: TKeyMap) { return < TData extends Partial< Record<keyof TKeyMap, unknown> > >( objectToMap: TData ): { [ K in keyof TData as K extends keyof TKeyMap ? TKeyMap[K] : never ]: TData[K] } => { // Typescript's going to have a hard time here. So I would // type this as any, do the things you need to do and cover // this with some tests. const result = {} as any for (const [key, value] of Object.entries(objectToMap)) { const mappedKey = map[key] if (mappedKey) result[mappedKey] = value } return result }}
其效果如您所料:
const mapMySchema = createMapper({ a: 'XX', b: 'YY', c: 'ZZ',})const body = mapMySchema({ a: 'aee', b: 123,})// type: { XX: string, YY: number }// value: { XX: 'aee', YY: 123 }