TypeScript_Challenges
前言
开始提前批直通车啦~~ 本篇带来 ts 超集的各种挑战加个人解说。源码地址:传送门
Hello World
1 | type HelloWorld = string; |
Pick
code
1 | type MyPick<T, K> = {}; |
解释:pick 就是获取第一个参数的类型的其中几个,写法是Pick<target,'属性1'|'属性2'>
,所以要实现他,就是说第二个属性要在 T 中,extends keyof 保证 K 的所有的类型在 T 中,in 保证 P 所有的类型在 K 中。
那么这里会有一个疑问。in 和 keyof 的区别
首先 keyof 是取 interface 的联合类型,
1 | interface UserInfo { |
in 是取联合类型,主要用于数组和对象的构建。
切记不要用于 interface 否则会报错,
1 | type name = "firstName" | "lastName"; |
用于实际开发,举例
1 | function getValue(obj: Object, key: string) { |
这样写就丧失了 ts 的优势
- 无法确定返回值的类型,
- 无法对 key 进行约束
所以我们要这样写
1 | function getValue<T extends Object, K extends keyof T>(obj: T, key: K): T[K] { |
Readonly
解释:需要将 T 中的参数变为只读
1 | type MyReadonly<T> = { |
Tuple to Object 元组转换为对象
解释:需要取出元组的值,并且构造成对象
1 | type TupleToObject<T extends readonly any[]> = { |
注意T[number]
是可以取出元组中的值,所以不用 keyof 了
第一个元素
解释:取出 T 的第一个元素 非空判断
1 | type First<T extends any[]> = T extends never[] ? never : T[0]; |
获取元组长度
解释:如果元组存在 length 属性则返回 length 值
1 | type Length<T extends readonly any[]> = T extends { length: infer L } |
注意输入的类型是数组才行
Exclude
解释:排除 T 中带有 U 的类型
1 | type MyExclude<T,U> = T extends U:never:T |
简述 T 的类型在 U 里面就排除
Awaited
解释:实现该方法返回 promise 对象类型,要点是判断其中类型是否为 promise 在判断 promise 内的类型是否还是 promise 类型(递归)
1 | type MyAwaited<T extends Promise<unknown>> = T extends Promise<infer U> |
If
解释:C 是 true 就返回 T 的类型,是 false 就返回 F 的类型
1 | type If<C extends Boolean, T, F> = C extends true ? T : F; |
Concat
解释:实现 js 的 concat 效果,合并两个类型数组。
1 | type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]; |
使用解构即可。
Includes
解释:后一个类型如果包含在前面的类型中就返回 true 否则返回 false
1 | type Includes<T extends readonly any[], U> = T extends [infer F, ...infer R] |
Push
解释:实现 js 的 push
1 | type Push<T extends any[], U> = [...T, U]; |
Unshift
解释:实现 js 的 unshift
1 | type Unshift<T extends any[], U> = [U, ...T]; |
Parameters
解释:获取函数参数类型以数组输出
1 | type MyParameters<T extends (...args: any[]) => any> = T extends ( |
注意的点在于其实...args
的类型是any[]
也就是以为着结果以数组输出的使用也可以利用上,直接使用 infer 去推断他的具体类型。
获取函数返回类型
解释:获取函数的返回类型
1 | type MyReturnType<T> = T extends (...args: any[]) => infer K ? K : never; |
和上一题类似
Omit
解释:Pick 和 Exclude 的结合
1 | type MyOmit<T, K extends keyof T> = { |
Readonly2
解释:如果不传 K 则默认为 readonly 否则就按 K 的内容对 T 进行只读的限制。
1 | type MyReadonly2<T, K extends keyof T = keyof T> = { |
注意的点首先是默认赋值,因为他不存在其实就是相当于用了全部的 T 的键,然后还有一点就是,他是 readonly 和 omit 的结合。在 K 中的所有值都是 readonly,不在 k 中的所有值都是 omit
Deep Readonly
解释:使用深度只读将对象的每个属性包括对象本身变成只读
1 | type DeepReadonly<T> = keyof T extends never |
注意其实递归设置 readonly 是比较简单的,但是细节在于递归的设置,此处如果不是对象就返回初始类型
元组转集合
解释:把元组转为集合
1 | type TupleToUnion<T> = T extends (infer U)[] ? U : never; |
关键在于通过 infer 推断集合的类型,以及取出元组的类型
可串联构造器
最后一个元素
解释:取出数组最后一个元素
1 | type Last<T extends any[]> = T extends [...any, infer U] ? U : never; |
Pop
解释:实现 pop
1 | type Pop<T extends any[]> = T extends [...infer Head, infer R] |
Promise.all
解释:实现 PromiseAll 获取数组值的类型
1 | type AwaitedAll<T extends any[]> = T extends [] |
- 结合 awaited 的学习知识获取 Promise 的类型
- 结合类型解构和递归处理 Promise 的情况
- 注意空数组的情况
- 因为 promise 的结果是不能改变的,所以要加 readonly
- 解构 T 类型传入自定义的 AwaitedAll
Type Lookup
解释:从 U 中获取 type 为 T 的类型
1 | type LookUp<U, T extends string> = U extends { type: T } ? U : never; |
Trim Left
解释:删除字符串左边的空格
1 | type Space = " " | "\n" | "\t"; |
技巧:结合模板字符串操作递归删除
Trim
解释:实现 trim 删除字符串两端的所有空格
1 | type Space = " " | "\n" | "\t"; |
先删左边再删右边
Capitalize
解释:大写第一个字母
1 | type MyCapitalize<S extends string> = S extends `${infer F}${infer R}` |
不能使用[infer F,...infer R]
是因为我们解析的是单个字符串
Replace
实现 Replace 替换
1 | type Replace< |