type-challenges刷题
发表于:2024-04-10 |

前言

锻炼一下自己的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
2
3
4
5
6
7
8
interface Todo {
title: string
description: string
completed: boolean
}

// TodoPreview 只包含 title 、completed 两个字段了
type TodoPreview = Pick<Todo, "title" | "completed">

接下来,我们要自己实现一个Pick,答案如下

1
2
3
type MyPick<T extends Record<PropertyKey, any>, K extends keyof T> = {
[P in K]: T[P];
}

这一段是什么意思呢,也很好理解,我们定义的泛型T,K,其中K很明显是T的键,T我们默认是一个对象,如何定义一个对象呢,我们可以用Record来定义,第一个参数是对象的key,第二个是value,其中type PropertyKey = string | number | symbol可以包含我们key的值,主要实现是同 in 关键字将 联合类型的每个值取出。然后,通过 索引类型 访问该字段的类型

对象属性只读

同样利用 in 和 索引类型,为每个字段加上 readonly 修饰符

1
2
3
type MyReadonly<T extends Record<PropertyKey, any>> = {
readonly [P in keyof T]: T[P];
};

元组转换为对象

通过 索引类型,直接取出所有的值。

1
2
3
type TupleToObject<T extends readonly any[]> = {
[P in T[number]]: P;
}

第一个元素

通过 条件类型 配合 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
2
3
type MyExclude<T extends keyof any | Function, K extends keyof any | Function> = T extends K
? never
: T;

Awaited

条件语句 配合 infer,加上递归。

1
2
3
4
5
type MyAwaited<T extends PromiseLike<any>> =  T extends PromiseLike<infer Res>
? Res extends PromiseLike<any>
? MyAwaited<Res>
: Res
: never;

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
2
3
4
5
6
7
8
9
10
11
12
13
14
// 这里通过函数类型,来解决修饰符和联合类型分布式问题
type Equals<A, B> = (<T>() => T extends A ? 1 : 2) extends <U>() => U extends B
? 1
: 2
? true
: false;

type Includes<T extends any[], Q> = T extends [infer First, ...infer Rest]
? Equals<Q, First> extends true
? true
: Rest["length"] extends 0
? false
: Includes<Rest, Q>
: false;

这段代码看起来很复杂,但可以分解为以下步骤:

首先是 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
2
3
const foo = (arg1: string, arg2: number): void => {}

type FunctionParamsType = Parameters<typeof foo> // [arg1: string, arg2: number]

通过 条件类型,加infer 实现。

1
2
3
4
5
6
type FunctionType = (...args: any[]) => any;
type MyParameters<T extends FunctionType> = T extends (
...arg: infer Params
) => any
? Params
: null;

中等(medium)

获取函数返回类型

infer 获取即可

1
2
3
4
5
6
type FunctionType = (...args: any[]) => any;
type MyReturnType<T extends FunctionType> = T extends (
...args: any[]
) => infer R
? R
: never;

实现 Omit

先取出所有键组成的联合类型,然后通过 Exclude 排除 需要排除的键。再通过映射类型实现。

1
2
3
type MyOmit<T extends Record<PropertyKey,any>, K> = {
[P in Exclude<keyof T, K>]: T[P];
}

对象部分属性只读

因为泛型可能为空,所以需要通过 = 来赋默认值
因为交叉类型使用了 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
2
3
4
5
type MyReadonly2<T, K extends keyof T = keyof T> = {
[p in keyof T as p extends K? never: p]: T[p]
} & {
readonly [p in K]: T[p]
}

对象属性只读(递归)

利用递归实现,判断当前是否是一个对象类型,是则递归。注意函数的处理

1
2
3
4
5
type DeepReadonly<T extends Record<PropertyKey, any>> = {
readonly [P in keyof T]: T[P] extends Function
? T[P]
: DeepReadonly<T[P]>;
}

元组转合集

这个没啥好说的,就直接取值就行

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
2
3
{
[P in keyof T as P extends U ? never : P]: T[P];
} & { [P in U]: K }

让我们分解这个表达式:

[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
2
3
4
5
6
7
8
9
10
11
type Chainable<T = {}> = {
option<U extends string, K>(
key: U,
value: K
): Chainable<
{
[P in keyof T as P extends U ? never : P]: T[P];
} & { [P in U]: K }
>;
get(): T;
};

最后一个元素

判断是否有长度,如果没有直接never,然后依次取值,如果数组长度还是大于1,即Rest不等于0,就循环调用,否则就取值第一个

1
2
3
4
5
6
7
8
type Last<T extends any[]> =
T['length'] extends 0
?never
:T extends [infer R, ...infer Rest]
?Rest["length"] extends 0
?R
:Last<Rest>
:never

排除最后一项

当length为1和0的时候都返回空数组,有长度的时候,将前面的展开,最后一项排除,取前面的值

1
2
3
4
5
6
7
type Pop<T extends any[]> = T['length'] extends 0
?[]
:T['length'] extends 1
?[]
:T extends [...infer Rest,infer R]
?Rest
:never

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
2
type PromiseFlat<T> = T extends Promise<infer R> ? PromiseFlat<R> : T;
declare function PromiseAll<T extends any[]>(values: readonly [...T]): Promise<{[P in keyof T]: PromiseFlat<T[P]>}>

查找类型

因为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
2
3
4
5
6
7
8
9
10
type DeleteChar = " " | "\n" | "\t";
type TrimLeft<T extends string> = T extends `${DeleteChar}${infer Rest}`
? TrimLeft<Rest>
: T;

type TrimRight<T extends string> = T extends `${infer Rest}${DeleteChar}`
? TrimRight<Rest>
: T;

type Trim<T extends string> = TrimLeft<TrimRight<T>>;

Capitalize

这个也是利用模板字符串和利用TS内置的Uppercase实现大写

1
type MyCapitalize<T extends string> = T extends `${infer F}${infer Rest}`? `${Uppercase<F>}${Rest}`: T; 
上一篇:
搞点科技-Clash for Windows
下一篇:
JSDOC速通