携手创作,共同成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情
1. 引言
接着上一节中,接下来我们继续Ts中等篇的题型练习
https://github.com/type-challenges/type-challenges/blob/main/README.zh-CN.md 提供的TypeScript 类型体操姿势合集题型,目的是为了让大家更好的了解TS的类型系统,编写自己的类型工具,或者单纯的享受挑战的乐趣!
2. 题型
- LastIndexOf:实现Array的类型版本。lastIndexOf LastIndexOf<T, U>取数组T中的任意U,并返回数组T中最后一个U的下标
type Res1 = LastIndexOf<[1, 2, 3, 2, 1], 2> // 3
type Res2 = LastIndexOf<[0, 0, 0], 2> // -1
思路: 首先,该题需要通过解构的方式在配合Equal 对比是否是目标检测字符串即可,值得注意的是因为是寻找数组最后一个符合U类型的下标,所以我们应该从数组末端检测;这样匹配到的就是需要寻找的数组末端下标。 解答:
type LastIndexOf<T, U> = T extends [...infer M, infer N] ?
Equal<N, U> extends true ? M['length'] : LastIndexOf<M, U>
: -1
- Unique:实现Lodash的类型版本。uniq, Unique接受数组T,返回没有重复值的数组T。
type Res = Unique<[1, 1, 2, 2, 3, 3]>; // expected to be [1, 2, 3]
type Res1 = Unique<[1, 2, 3, 4, 4, 5, 6, 7]>; // expected to be [1, 2, 3, 4, 5, 6, 7]
type Res2 = Unique<[1, "a", 2, "b", 2, "a"]>; // expected to be [1, "a", 2, "b"]
type Res3 = Unique<[string, number, 1, "a", 1, string, 2, "b", 2, number]>; // expected to be [string, number, 1, "a", 2, "b"]
type Res4 = Unique<[unknown, unknown, any, any, never, never]>; // expected to be [unknown, any, never]
思路: 首先,这题需要借助辅助数组,通过递归的方式,依次把数组中没有的内容塞进去
Includes<R, U> extends true
? Unique<F, [...U]>
: Unique<F, [...U, R]>
这里就是递归的核心逻辑,如果数组 U 中不包含该元素,那么我们就塞进去,否则有就不塞 一开始判断数组中是否存在该元素,采用的是 R extends U[number] 但是发现有很多 case 没有考虑到;因此我们需要实现一个 Includes 方法来判断是否有该值,这个方法也是常规的递归实现; 解答:
// 答案
type Includes<T, U> = U extends [infer F, ...infer Rest]
? Equal<F, T> extends true
? true
: Includes<T, Rest>
: false;
type Unique<T, U extends any[] = []> =
T extends [infer R, ...infer F]
? Includes<R, U> extends true
? Unique<F, [...U]>
: Unique<F, [...U, R]>
: U
- MapTypes:实现MapTypes<T, R>;将对象T中的类型转换为类型R定义的不同类型,类型R具有以下结构
type StringToNumber = { mapFrom: string; mapTo: number;}
MapTypes<{iWillBeANumberOneDay: string}, StringToNumber> // gives { iWillBeANumberOneDay: number; }
// 注意user可以提供类型的联合:
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 }
type StringToNumber = {
mapFrom: string; // value of key which value is string
mapTo: number; // will be transformed for number
}
思路: 这道题,意思是叫我们把T的类型按照R的类型规则进行转换。所以我们第一步先使用类型映射获得每个属性值,在拿获取的属性值和R['mapFrom'] 进行对比,如果是则在拿R extends { mapFrom: T[K] } 进行比较,如果为true说明可以转换,则目标属性值转为R['mapTo'];该题完成,结束。 解答:
type MapTypes<T, 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]
}