前言
锻炼一下自己的ts的能力,本文将长期置顶,和力扣刷题一样。本篇持续更新~
安装并使用
大家去github上把代码拉一下按照他的文档启用就行
地址:https://github.com/type-challenges/type-challenges
热身(warm)
这个太简单了
1 | type HelloWorld = string // expected to be a string |
简单(easy)
实现 Pick
首先我们都知道Pick在TS中是如何使用的,他的意思是Pick 可以从一个对象类型中 取出某些属性
比如这个例子
1 | interface Todo { |
接下来,我们要自己实现一个Pick,答案如下
1 | type MyPick<T extends Record<PropertyKey, any>, K extends keyof T> = { |
这一段是什么意思呢,也很好理解,我们定义的泛型T,K,其中K很明显是T的键,T我们默认是一个对象,如何定义一个对象呢,我们可以用Record来定义,第一个参数是对象的key,第二个是value,其中type PropertyKey = string | number | symbol
可以包含我们key的值,主要实现是同 in 关键字将 联合类型的每个值取出。然后,通过 索引类型 访问该字段的类型
对象属性只读
同样利用 in 和 索引类型,为每个字段加上 readonly 修饰符
1 | type MyReadonly<T extends Record<PropertyKey, any>> = { |
元组转换为对象
通过 索引类型,直接取出所有的值。
1 | type TupleToObject<T extends readonly any[]> = { |
第一个元素
通过 条件类型 配合 infer 消费传入的泛型。
1 | type First<T extends any[]> = T extends [infer R, ...infer Rest] ? R : never; |
获取元组长度
获取属性值length即可
1 | type Length<T extends readonly any[]> = T['length'] |
实现 Exclude
我们当要排除选中的属性,只需要遇到选中属性时,让其返回 never 这样就排除了
1 | type MyExclude<T extends keyof any | Function, K extends keyof any | Function> = T extends K |
Awaited
条件语句 配合 infer,加上递归。
1 | type MyAwaited<T extends PromiseLike<any>> = T extends PromiseLike<infer Res> |
If
直接通过通过条件类型实现
1 | type If<C, T, F> = C extends boolean ? (C extends true ? T : F) : never; |
Concat
直接接收泛型,然后将其展开即可
1 | type Concat<T extends readonly any[], U extends readonly any[]> =[...T,...U] |
Includes
1 | // 这里通过函数类型,来解决修饰符和联合类型分布式问题 |
这段代码看起来很复杂,但可以分解为以下步骤:
首先是 Equals 类型,它用于判断两个类型是否相等:(<T>() => T extends A ? 1 : 2)
:这是一个函数类型,它接受一个类型参数 T,然后判断 T 是否可以赋值给 A。如果可以,则返回 1,否则返回 2。<U>() => U extends B ? 1 : 2
:类似地,这也是一个函数类型,用于判断 U 是否可以赋值给 B。(<T>() => T extends A ? 1 : 2) extends (<U>() => U extends B ? 1 : 2)
:这是一个比较类型的操作,它比较了两个函数类型。如果左侧的函数类型可以赋值给右侧的函数类型,则结果为 true,否则为 false。
? true : false:根据上一步的比较结果,返回 true 或 false。
这样,Equals<A, B>
类型就可以判断类型 A 和 B 是否相等。
接着是 Includes 类型,它用于判断数组是否包含指定的元素类型:T extends [infer First, ...infer Rest]
:首先,检查数组 T 是否以 First 开头,并将剩余部分赋值给 Rest。Equals<Q, First>
:调用上面定义的 Equals 类型,判断 Q 是否与数组的第一个元素 First 相等。Rest["length"] extends 0
:检查剩余数组的长度是否为 0,即数组是否为空。
如果 Q 与 First 相等,则返回 true。如果数组为空,则返回 false。否则,递归调用 Includes 类型,继续在剩余的数组部分中查找是否包含 Q 元素。
通过这种方式,Includes 类型能够正确地判断数组是否包含指定的元素类型,并返回相应的结果类型。
Push
展开操作就行
1 | type Push<T extends any[], U> = [...T,U] |
Unshift
展开操作就行
1 | type Unshift<T extends any[], U> = [U,...T] |
实现Parameters
首先我们来了解一下Parameters的作用,大概就是这样
1 | const foo = (arg1: string, arg2: number): void => {} |
通过 条件类型,加infer 实现。
1 | type FunctionType = (...args: any[]) => any; |
中等(medium)
获取函数返回类型
infer 获取即可
1 | type FunctionType = (...args: any[]) => any; |
实现 Omit
先取出所有键组成的联合类型,然后通过 Exclude 排除 需要排除的键。再通过映射类型实现。
1 | type MyOmit<T extends Record<PropertyKey,any>, K> = { |
对象部分属性只读
因为泛型可能为空,所以需要通过 = 来赋默认值
因为交叉类型使用了 T extends K ? never : T 的判断,所以默认值应该是 keyof T
为了避免readonly等修饰符,因此使用了
1 | [p in keyof T as p extends K? never: p] |
而非
1 | [p in Exclude<keyof T,K>] |
最终结果为
1 | type MyReadonly2<T, K extends keyof T = keyof T> = { |
对象属性只读(递归)
利用递归实现,判断当前是否是一个对象类型,是则递归。注意函数的处理
1 | type DeepReadonly<T extends Record<PropertyKey, any>> = { |
元组转合集
这个没啥好说的,就直接取值就行
1 | type TupleToUnion<T extends any[]> = T[number] |
可串联构造器
让我们逐步解释这个类型的含义:
type Chainable<T = {}>
:这是一个泛型类型,表示一个具有类型为 T 的初始对象的链式调用结构。
option<U extends string, K>(key: U, value: K): Chainable<...>
:这是一个方法签名,表示 Chainable 对象上的 option 方法。它接受两个参数:key 和 value。key 参数必须是 string 类型,而 value 参数的类型则是任意类型 K。
Chainable<...>
:option 方法返回一个新的 Chainable 类型对象,其类型由以下表达式确定:
1 | { |
让我们分解这个表达式:
[P in keyof T as P extends U ? never : P]
:这部分使用了 TypeScript 的条件类型。它迭代类型 T 的所有属性 P,并根据条件 P extends U 进行筛选。如果 P 能够赋值给 U 类型,则将其排除(使用 never 类型),否则保留。
{ [P in U]: K }
:这部分表示在新对象中添加一个属性,属性名为 U,属性值类型为 K。
所以,option 方法的作用是向 Chainable 对象中添加一个键值对,其中键名为 key,键值为 value,并返回新的 Chainable 对象。get()
: T:这是 Chainable 对象上的另一个方法 get,它不接受任何参数,并返回当前 Chainable 对象的值,类型为 T。
1 | type Chainable<T = {}> = { |
最后一个元素
判断是否有长度,如果没有直接never,然后依次取值,如果数组长度还是大于1,即Rest不等于0,就循环调用,否则就取值第一个
1 | type Last<T extends any[]> = |
排除最后一项
当length为1和0的时候都返回空数组,有长度的时候,将前面的展开,最后一项排除,取前面的值
1 | type Pop<T extends any[]> = T['length'] extends 0 |
Promise.all
这段代码定义了两个 TypeScript 类型和一个函数:
PromiseFlat<T>
:这是一个条件类型,用于展平嵌套的 Promise。它接受一个类型参数 T,并根据 T 的类型进行处理。如果 T 是一个 Promise 类型 Promise<infer R>
,则递归地调用 PromiseFlat<R>
,直到 T 不再是 Promise 类型,然后返回最终的非 Promise 类型。换句话说,它会递归地将嵌套的 Promise 展平为非 Promise 类型。
PromiseAll<T>
:这是一个函数签名,它接受一个泛型数组 values,其中的每个元素都是 Promise 或非 Promise 类型。函数返回一个 Promise,其解决值是一个对象,其中包含了输入数组中每个元素的结果。具体来说,它的返回类型是一个 Promise,解决值是一个元组,每个元组成员的类型都经过了 PromiseFlat 处理。
现在让我们分解一下 PromiseAll 函数签名的关键部分:
T extends any[]
:这是一个泛型约束,确保传入的参数 values 是一个数组。
values: readonly [...T]
:这声明了一个只读的元组类型参数 values,其中的每个元素的类型由 T 决定。
Promise<{[P in keyof T]: PromiseFlat<T[P]>}>
:这是函数的返回类型。它使用了映射类型 {[P in keyof T]: ...}
,遍历输入数组 T 的每个元素,并对其应用 PromiseFlat 类型,以确保最终结果不包含嵌套的 Promise。因此,函数返回一个 Promise,其解决值是一个元组,其中每个元素的类型都是经过 PromiseFlat 处理的。
1 | type PromiseFlat<T> = T extends Promise<infer R> ? PromiseFlat<R> : T; |
查找类型
因为TS的类型判断是鸭子类型,所以我们可以基于该类型系统进行判断。
1 | type LookUp<U, T> = U extends {type: T} ? U : never; |
去除左侧空白
利用模板字符串
1 | type TrimLeft<T extends string> = T extends `${" " | "\n" | "\t"}${infer Rest}`? TrimLeft<Rest> : T; |
去除两端空白字符
利用模板字符串去除左边右边空白字符
1 | type DeleteChar = " " | "\n" | "\t"; |
Capitalize
这个也是利用模板字符串和利用TS内置的Uppercase实现大写
1 | type MyCapitalize<T extends string> = T extends `${infer F}${infer Rest}`? `${Uppercase<F>}${Rest}`: T; |