type-challenges:MapTypes

57 阅读2分钟

MapTypes

问题描述

实现MapTypes<T, R>将对象 T 中的类型转换为类型 R 定义的不同类型的实现,其结构如下

type StringToNumber = {
  mapFrom: string; // value of key which value is string
  mapTo: number; // will be transformed for number
}

举例:

type StringToNumber = { mapFrom: string; mapTo: number;}
MapTypes<{iWillBeANumberOneDay: string}, StringToNumber> // gives { iWillBeANumberOneDay: number; }

请注意,用户可以提供联合类型:

type StringToNumber = { mapFrom: string; mapTo: number;}
type StringToDate = { mapFrom: string; mapTo: Date;}
MapTypes<{iWillBeNumberOrDate: string}, StringToDate | StringToNumber> // gives { iWillBeNumberOrDate: number | Date; }

如果我们的 map 中不存在该类型,则保持原样:

type StringToNumber = { mapFrom: string; mapTo: number;}
MapTypes<{iWillBeANumberOneDay: string, iWillStayTheSame: Function}, StringToNumber> // // gives { iWillBeANumberOneDay: number, iWillStayTheSame: Function }
// ============= Test Cases =============
import type { Equal, Expect } from './test-utils';
​
type cases = [  Expect<Equal<MapTypes<{ stringToArray: string }, { mapFrom: string; mapTo: [] }>, { stringToArray: [] }>>,
  Expect<Equal<MapTypes<{ stringToNumber: string }, { mapFrom: string; mapTo: number }>, { stringToNumber: number }>>,
  Expect<Equal<MapTypes<{ stringToNumber: string; skipParsingMe: boolean }, { mapFrom: string; mapTo: number }>, { stringToNumber: number; skipParsingMe: boolean }>>,
  Expect<Equal<MapTypes<{ date: string }, { mapFrom: string; mapTo: Date } | { mapFrom: string; mapTo: null }>, { date: null | Date }>>,
  Expect<Equal<MapTypes<{ date: string }, { mapFrom: string; mapTo: Date | null }>, { date: null | Date }>>,
  Expect<Equal<MapTypes<{ fields: Record<string, boolean> }, { mapFrom: Record<string, boolean>; mapTo: string[] }>, { fields: string[] }>>,
  Expect<Equal<MapTypes<{ name: string }, { mapFrom: boolean; mapTo: never }>, { name: string }>>,
  Expect<Equal<MapTypes<{ name: string; date: Date }, { mapFrom: string; mapTo: boolean } | { mapFrom: Date; mapTo: string }>, { name: boolean; date: string }>>,
]
​
​
// ============= Your Code Here =============
type MapTypes<T extends object, R extends { mapFrom: any; mapTo: any }> = {
  [K in keyof T]: T[K] extends R['mapFrom'] ? (R extends { mapFrom: T[K] } ? R['mapTo'] : never) : T[K]
}
​
​

首先观察测试用例发现测试用例3和7,mapFrom 的类型并不一定和第一个泛型参数的属性值类型相同,所以在约束时不可以将泛型 R 约束为 napFrom:T[keyof T] ,否则测试用例7不通过,其次我们可以看到第一个泛型参数传入什么值,返回值也会有当前的全部属性,所以可以确定,键名的约束为[K in keyof T],键值的约束我们要确定当前键值的类型和泛型 R 的属性 mapFrom的类型相同,如果不考虑联合类型,使用 T[K] extends R['mapFrom']已经可以通过大部分测试用例了,但是在面对测试用例8的时候,这里的 R['mapFrom'] 将会是联合类型 string|Date,所以 T[K] 的类型并不能得到约束,date:Date 将返回 mapTo 的联合类型 string|boolean,即使将判断条件稍作修改这样

type MapTypes<T extends object, R extends { mapFrom:any; mapTo: any }> = {
  [K in keyof T]: Equal<T[K] , R['mapFrom'] > extends true ? R['mapTo'] : T[K]
}

这里测试用例8的结果为 {name: string;date: Date;},仍然不满足条件,所以需要继续判断一次,(R extends { mapFrom: T[K] } 只有这样才能将不符合条件的 { mapFrom: string; mapTo: boolean } 排除。