Skip to content

Commit 6822c3b

Browse files
📝 update easy topic
1 parent b8eb4aa commit 6822c3b

File tree

7 files changed

+346
-2
lines changed

7 files changed

+346
-2
lines changed

docs/.vitepress/config.mts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,15 @@ export default defineConfig({
2323
text: '挑战',
2424
items: [
2525
{ text: '挑战介绍', link: '/challenges/' },
26-
{ text: '简单', link: '/challenges/easy/' },
26+
{ text: '简单',
27+
items: [
28+
{ text: '实现Pick', link: '/challenges/easy/pick' },
29+
{ text: '元组转换为对象', link: '/challenges/easy/tuples' },
30+
{ text: '实现Readonly', link: '/challenges/easy/readonly' },
31+
{ text: '实现Awaited', link: '/challenges/easy/awaited' },
32+
{ text: '实现Parameters', link: '/challenges/easy/paramEters' },
33+
]
34+
},
2735
{ text: '中等', link: '/challenges/medium/' },
2836
{ text: '困难', link: '/challenges/hard/' },
2937
{ text: '地狱', link: '/challenges/extreme/' }

docs/challenges/easy/awaited.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
title: 实现Awaited
3+
---
4+
5+
# {{ $frontmatter.title }}
6+
7+
## 题目描述
8+
9+
假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型。在 TS 中,我们用 `Promise<T>` 中的 T 来描述这个 Promise 返回的类型。请你实现一个类型,可以获取这个类型。
10+
11+
例如:`Promise<ExampleType>`,请你返回 ExampleType 类型。
12+
13+
```ts
14+
type ExampleType = Promise<string>;
15+
16+
type Result = MyAwaited<ExampleType>; // string
17+
```
18+
19+
## 分析
20+
21+
```ts
22+
type MyAwaited<T> = T extends Promise<infer R> ? R : never;
23+
24+
type Case1 = MyAwaited<Promise<string>>; // string
25+
```
26+
27+
但是实际的场景中,还会存在 `Promise` 嵌套的场景:
28+
29+
```ts
30+
type MyAwaited<T> = T extends Promise<infer R> ? R : never;
31+
32+
type Case2 = MyAwaited<Promise<Promise<string>>>; // Promise<string>
33+
```
34+
35+
此时由于嵌套,并不能得到预期的最终的返回类型。此时就需要递归上场了,简单改写,如下:
36+
37+
```ts
38+
type MyAwaited<T> = T extends Promise<infer R> ? MyAwaited<R> : T;
39+
40+
type Case2 = MyAwaited<Promise<Promise<string>>>; // string
41+
type Case3 = MyAwaited<Promise<Promise<Promise<string>>>>; // string
42+
```
43+
44+
## 题解
45+
46+
在题目的 Case 中,存在如下场景:
47+
48+
```ts
49+
type T = { then: (onfulfilled: (arg: number) => any) => any };
50+
51+
// 期望 MyAwaited<T> = number
52+
```
53+
54+
也就是还需要处理 类似 promise 的场景,根据题目 case,可以写出如下代码:
55+
56+
```ts
57+
type MyAwaited<T> = T extends
58+
| Promise<infer R>
59+
| { then: (onfullfilled: (arg: infer R) => any) => any }
60+
? MyAwaited<R>
61+
: T;
62+
```
63+
64+
利用 `|` 覆盖 普通的 `Promise``then` 两种场景。
65+
66+
这里还有一点值得一提的是,当联合类型位于 `extends` 右侧时,并没有分发特性,虽然判断会做多次,但是其多次判断的结果会以或的方式合并后交由 `extends` 的逻辑处理,比如,`'a' extends 'a' | 'b' ? 1 : 2`,此时,可以理解为会进行 `'a' extends 'a'` 以及 `'a' extends 'b'`两次判断,两者有一处为 true 即返回 1,否则返回 2。但是并不会返回 `1 | 2`
67+
68+
## 知识点
69+
70+
1. `A extends Promise<infer R>`,匹配推断类型
71+
2. 递归解决嵌套问题
72+
3. 联合类型位于 `extends` 右侧时不分发

docs/challenges/easy/index.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,3 @@ type MyReadonly<T> = {
5252
1. `[P in keyof T]` 遍历 对象类型(数组也是对象)
5353
2. `readonly` 增加修饰符即可
5454

55-
<CartoonPlane/>

docs/challenges/easy/paramEters.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
title: 实现Parameters
3+
---
4+
5+
# {{ $frontmatter.title }}
6+
7+
## 题目描述
8+
9+
实现内置的 `Parameters<T>` 类型,而不是直接使用它。
10+
11+
例如:
12+
13+
```ts
14+
const foo = (arg1: string, arg2: number): void => {};
15+
16+
// [arg1: string, arg2: number]
17+
type FunctionParamsType = MyParameters<typeof foo>;
18+
```
19+
20+
## 分析
21+
22+
使用 `A extends infer B` 这样的匹配推断,不过这里推断的是函数的参数。可以先从一个参数推断开始:
23+
24+
```ts
25+
// infer 处于第一个参数的位置,故可以得到第一个参数
26+
// 如果函数没有第一个参数,则会推断出来 unknown,并不会走 false 逻辑
27+
type MyFirstParameter<T> = T extends (arg: infer F) => any ? F : never;
28+
29+
// Case1 = number;
30+
type Case1 = MyFirstParameter<(a: number) => {}>;
31+
32+
// Case2 = unknown,特殊情况,没有参数
33+
type Case2 = MyFirstParameter<() => {}>;
34+
```
35+
36+
同样的套路,推断第二个参数 or 第三个参数也可以很快写出来。
37+
38+
那么推断所有参数类型呢?
39+
40+
可以通过扩展操作符进行。
41+
42+
## 题解
43+
44+
```ts
45+
type Parameters<T extends (...args: any) => any> =
46+
// 扩展操作符,推断出 P
47+
T extends (...args: infer P) => any ? P : never;
48+
```
49+
50+
## 知识点
51+
52+
1. 函数类型,也可以做推断匹配,`A extends (...args: infer P) => infer R`
53+
2. 函数类型,推断匹配时,使用扩展操作符
54+
55+
这里需要注意的就是写法,和元组的推断不同,函数参数的推断必须注明参数名称(名称随便起,并不重要,也不需要和实际参数名称一致,只是占个位置而已)。

docs/challenges/easy/pick.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
---
2+
title: 实现Pick
3+
---
4+
5+
# {{ $frontmatter.title }}
6+
7+
## 题目描述
8+
9+
实现 TS 内置的 `Pick<T, K>`,但不可以使用它。
10+
11+
**从类型 `T` 中选择出属性 `K`,构造成一个新的类型**
12+
13+
例如:
14+
15+
```ts
16+
interface Todo {
17+
title: string;
18+
description: string;
19+
completed: boolean;
20+
}
21+
22+
type TodoPreview = MyPick<Todo, 'title' | 'completed'>;
23+
24+
const todo: TodoPreview = {
25+
title: 'Clean room',
26+
completed: false,
27+
};
28+
```
29+
30+
## 分析
31+
32+
此题的核心在于对 对象类型 的遍历并修改,对 对象类型的遍历方法如下:
33+
34+
```ts
35+
type Copy<T> = {
36+
[P in keyof T]: T[P];
37+
};
38+
39+
type Case1 = Copy<{ a: string; b: string }>;
40+
```
41+
42+
`keyof T` 本身是个联合类型,`[P in keyof T]` 便是将联合类型取出作为新类型的键值, 在上例中,流程如下:
43+
44+
```ts
45+
// step1: keyof T: 'a' | 'b'
46+
// ['a']: T['a'] (也就是 string)
47+
// ['b']: T['b'] (也就是 string)
48+
// type Case1 = { a: string, b: string }
49+
type Case1 = Copy<{ a: 1; b: 2 }>;
50+
```
51+
52+
## 题解
53+
54+
理解了上述遍历的行为后,实现 `Pick` 就非常简单了,只需要把 `keyof T` 这个联合类型换成入参的 `K` 即可:
55+
56+
```ts
57+
type MyPick<T, K extends keyof T> = {
58+
[P in K]: T[P];
59+
};
60+
61+
// P in 'a' | 'b'
62+
// ['a']: T['a']
63+
// ['b']: T['b']
64+
// type Case2 = { a: string, b: string }
65+
type Case2 = MyPick<{ a: string; b: string; c: string }, 'a' | 'b'>;
66+
```
67+
68+
注意:上述需要对 `K` 进行约束,也就是 `K extends keyof T`,因为 K 如果不是类型的键值的话,需要进行类型约束,否则 `T[P]` 将会因为访问不到类型而报错。
69+
70+
## 知识点
71+
72+
1. `[P in keyof T]: T[P]` 遍历 对象类型/数组类型
73+
2. `[P in K]: T[P]` ,根据传入的 K 进行遍历,此时 K 需要满足 `K extends keyof T`
74+
75+
76+
<CartoonPlane/>

docs/challenges/easy/readonly.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
title: 实现Readonly
3+
---
4+
5+
# {{ $frontmatter.title }}
6+
7+
## 题目描述
8+
9+
不要使用内置的 `Readonly<T>`,自己实现一个。
10+
11+
`Readonly` 会接收一个 泛型参数,并返回一个完全一样的类型,只是所有属性都会被 `readonly` 所修饰。
12+
13+
也就是不可以再对该对象的属性赋值。
14+
15+
例如:
16+
17+
```ts
18+
interface Todo {
19+
title: string;
20+
description: string;
21+
}
22+
23+
const todo: MyReadonly<Todo> = {
24+
title: 'Hey',
25+
description: 'foobar',
26+
};
27+
28+
todo.title = 'Hello'; // Error: cannot reassign a readonly property
29+
todo.description = 'barFoo'; // Error: cannot reassign a readonly property
30+
```
31+
32+
## 分析
33+
34+
这一题,本质也是对对象类型进行遍历,然后为每一个属性增加 `readonly` 的修饰符,只需要在遍历的时候为每一个属性增加只读修饰符。
35+
36+
```ts
37+
type MyReadonly<T> = {
38+
readonly [P in keyof T]: T[P];
39+
};
40+
```
41+
42+
## 题解
43+
44+
```ts
45+
type MyReadonly<T> = {
46+
readonly [P in keyof T]: T[P];
47+
};
48+
```
49+
50+
## 知识点
51+
52+
1. `[P in keyof T]` 遍历 对象类型(数组也是对象)
53+
2. `readonly` 增加修饰符即可

docs/challenges/easy/tuples.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
---
2+
title: 元组转换为对象
3+
---
4+
5+
# {{ $frontmatter.title }}
6+
7+
## 题目描述
8+
9+
传入一个元组类型,将这个元组类型转换为对象类型,这个对象类型的键/值都是从元组中遍历出来。
10+
11+
例如:
12+
13+
```ts
14+
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const;
15+
16+
// expected { tesla: 'tesla', 'model 3': 'model 3',
17+
// 'model X': 'model X', 'model Y': 'model Y' }
18+
type result = TupleToObject<typeof tuple>;
19+
```
20+
21+
## 分析
22+
23+
此题目标是生成一个新的对象类型,其键值和属性值就是传入的元组的每一项的值,在前面的题目中我们了解了遍历一个对象的方法,加以改动,就可以改为生成一个对象的方法,如下,给定一个联合类型 `'a' | 'b'`,生成一个新的对象:
24+
25+
```ts
26+
// PropertyKey 是 ts 内置类型:type PropertyKey = string | number | symbol
27+
type Test<K extends PropertyKey> = {
28+
[P in K]: P;
29+
};
30+
31+
// ['a']: 'a'
32+
// ['b']: 'b'
33+
// Case1 = { a: 'a', b: 'b' }
34+
type Case1 = Test<'a' | 'b'>;
35+
```
36+
37+
目前就比较接近了,但是题目给的是元组,所以需要把元组转换成联合类型,可以使用官方提供的 `T[number]` 写法,即可将元组转换为联合类型,
38+
39+
```ts
40+
type Tuple = [string, number];
41+
42+
type Case2 = Tuple[number]; // string | number
43+
```
44+
45+
```ts
46+
// 平时工作中经常在不清楚全部属性名称的时候,会 [key: string] 来代替具体的属性名称
47+
type MyObject<T> = {
48+
[key: string]: T;
49+
};
50+
51+
// Case1 = T = number | string
52+
type Case1 = MyObject<number | string>[string];
53+
```
54+
55+
而数组的 `T[number]` 访问与此类似:
56+
57+
```ts
58+
// 类数组的类型声明
59+
type MyArrayLike<T> = {
60+
[key: number]: T;
61+
};
62+
// MyArrayLike<string> 的属性有 number
63+
// 所以可以通过索引签名访问的特性访问到 MyArrayLike<string>[number]
64+
type Case3 = ArrayLike<string>[number];
65+
```
66+
67+
## 题解
68+
69+
了解了元组转为联合类型的方法后,答案也就呼之欲出了:
70+
71+
```ts
72+
type TupleToObject<T extends readonly PropertyKey[]> = {
73+
[P in T[number]]: P;
74+
};
75+
```
76+
77+
这里也是,需要对输入的元组进行类型限制,其元素必须是 `PropertyKey`(ts 内置类型: `type PropertyKey = string | number | symbol`)。
78+
79+
## 知识点
80+
81+
1. `T[number]` 索引签名访问,元组转联合类型

0 commit comments

Comments
 (0)