Skip to content

Commit b8d9be3

Browse files
🚑 fix build error
1 parent eab96ca commit b8d9be3

File tree

14 files changed

+146
-60
lines changed

14 files changed

+146
-60
lines changed

docs/.vitepress/config.mts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export default defineConfig({
3030
{ text: '实现Readonly', link: '/challenges/easy/readonly' },
3131
{ text: '实现Awaited', link: '/challenges/easy/awaited' },
3232
{ text: '实现Parameters', link: '/challenges/easy/paramEters' },
33+
{ text: '实现Exclude', link: '/challenges/easy/Exclude' },
34+
{ text: '第一个元素', link: '/challenges/easy/firstOne' },
3335
]
3436
},
3537
{ text: '中等',
@@ -52,7 +54,6 @@ export default defineConfig({
5254
{ text: '实现Promise.all', link: '/challenges/medium/Promise.all' },
5355
{ text: '实现Replace', link: '/challenges/medium/Replace' },
5456
{ text: '实现KebabCase', link: '/challenges/medium/KebabCase' },
55-
{ text: '实现Pop', link: '/challenges/medium/Pop' },
5657
{ text: '最后一个元素', link: '/challenges/medium/lastOne' },
5758
]
5859
},

docs/challenges/easy/Exclude.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
title: 实现Exclude
3+
---
4+
5+
# {{ $frontmatter.title }}
6+
7+
## 🎯 题目描述
8+
9+
实现内置的 Exclude <T, U>类型,但不能直接使用它本身。
10+
11+
> 从联合类型 T 中排除 U 的类型成员,来构造一个新的类型。
12+
13+
例如:
14+
15+
```ts
16+
type Result = MyExclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'
17+
```
18+
19+
## 🔍 分析
20+
21+
`A extends B ? true : false` 中,如果 `A` 是联合类型,那么就会触发 ts 的分发特性。
22+
23+
```ts
24+
type Example<T> = T extends string ? T : boolean;
25+
// 1 | '3' | {} | [] extends string 会触发分发特性,会转变成如下逻辑:
26+
// 需要注意的是 T 在分发后,还是用 T 表示,但是此时的 T 仅仅表示的是联合类型中的那一项,而非整个联合类型
27+
// 1 extends string ? 1 : boolean | '3' extends string ? '3' : boolean | {} extends string ? {} : boolean | [] extends string ? [] : boolean
28+
// boolean | '3' | boolean | boolean
29+
// Case1 = boolean | '3'
30+
type Case1 = Example<1 | '3' | {} | []>;
31+
```
32+
33+
要注意的是,只有泛型才会触发分发特性,也就是说,如下的简单类型的判断是不会触发分发特性的:
34+
35+
```ts
36+
type Example<T> = T extends string ? 1 : 2;
37+
38+
// 简单类型,不会分发,结果为 2
39+
type Case2 = '1' | 1 extends string ? 1 : 2;
40+
41+
// 泛型,触发分发
42+
// '1' extends string ? 1 : 2 | 1 extends string ? 1 : 2
43+
// 1 | 2
44+
// 结果为 1 | 2
45+
type Case3 = Example<'1' | 1>;
46+
```
47+
48+
了解了分发特性后,这题的结果就呼之欲出了,但是在此之前,还有一个特性需要了解,那就是 **任意类型 | never = 任意类型**
49+
50+
## 🛠️ 题解
51+
52+
解法如下:
53+
54+
```ts
55+
type MyExclude<T, U> = T extends U ? never : T;
56+
57+
// 触发分发特性
58+
// 'a' extends 'a' ? never : 'a' | 'b' extends 'a' ? never : 'b' | 'c' extends 'a' ? never : 'c'
59+
// never | 'b' | 'c'
60+
// Case = 'b' | 'c';
61+
type Case = MyExclude<'a' | 'b' | 'c', 'a'>;
62+
```
63+
64+
## 💡 知识点
65+
66+
1. 泛型下联合的分发特性
67+
2. `任意类型 | never = 任意类型`

docs/challenges/easy/firstOne.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
title: 第一个元素
3+
---
4+
5+
# {{ $frontmatter.title }}
6+
7+
## 🎯 题目描述
8+
9+
实现一个通用`First<T>`,它接受一个数组`T`并返回它的第一个元素的类型。
10+
11+
例如:
12+
13+
```ts
14+
type arr1 = ['a', 'b', 'c'];
15+
type arr2 = [3, 2, 1];
16+
17+
type head1 = First<arr1>; // expected to be 'a'
18+
type head2 = First<arr2>; // expected to be 3
19+
```
20+
21+
## 🔍 分析
22+
23+
这一题主要是围绕元组的,要获取元组的第一个,就必须要借助 ts 的 `infer` 了。
24+
25+
这一类题的套路都如下:`T extends [infer F, ...infer R] ? F : never`
26+
27+
其本质可以说是模式匹配,如果 `T` 能够匹配上 `[infer F, ...infer R]`,那么就取前一个类型,否则走后者,基本逻辑和 js 中的三元表达式一样。
28+
29+
## 🛠️ 题解
30+
31+
了解了 infer 和 extends 用法后,其实答案也非常简单了:
32+
33+
```ts
34+
type First<T extends any[]> = T extends [infer F, ...infer _] ? F : never;
35+
```
36+
37+
这里值得一提的就是 `[] extends [infer F, ...infer R]` 的判断,由于有两个变量需要推断,`F``R`,但是原数组中一个元素都没有,此时就会走 false 的逻辑,返回 never。而如果有一个元素,那么还是会走 true 的逻辑,此时 `R` 会被推断为 `[]`
38+
39+
## 💡 知识点
40+
41+
1. `xxx extends infer xxx ? A : B`,infer + extends 范式
42+
2. `[infer F, ...infer R]` 中,如果 1 个元素都没有,那么走 false 的逻辑
43+
44+
## 其他解题思路
45+
46+
上面的解题思路是利用`infer`进行匹配, 还有一种完全不同的思路, 是利用元组的索引特性, 我们通过`T[number]`可以获取任意位置的元素, 还能通过`T['length']`获取元组的长度, 类比于 js 中国呢我们获取第一个元素的方式, 我们可以写出如下伪代码:
47+
48+
```ts
49+
// 利用isEmpty判断数组是否为空
50+
Array.isEmpty(arr) ? undefined : arr[0];
51+
52+
// 或者利用length判断数组是否为空
53+
arr.length === 0 ? undefined : arr[0];
54+
```
55+
56+
将上述伪代码改写成 typescript 类型版本:
57+
58+
```ts
59+
type First<T extends any[]> = T['length'] extends 0 ? never : T[0];
60+
61+
// 或者
62+
type First<T extends any[]> = T extends [] ? never : T[0];
63+
```

docs/challenges/medium/Diff.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type Result2 = Diff<Bar, Foo>; // { b: number, c: boolean }
2424

2525
## 🔍 分析
2626

27-
这道题目其实就比较宽泛了,获取只存在于 A 或 只存在于 B 中的属性,网上有很多借助 `Omit`, `Exclude`, `&` 的解法,我认为都不太直观,其实借助 `as` 非常好实现。关于 `as` 可以参考 [实现 Omit](/medium/实现Omit.md) 中介绍的部分。
27+
这道题目其实就比较宽泛了,获取只存在于 A 或 只存在于 B 中的属性,网上有很多借助 `Omit`, `Exclude`, `&` 的解法,我认为都不太直观,其实借助 `as` 非常好实现。关于 `as` 可以参考 [实现 Omit](./Omit.md) 中介绍的部分。
2828

2929
首先通过 `keyof A | keyof B` 可以获取所有的属性,接下来只需要让即存在于 A 中的属性 又存在于 B 中的属性为 never 即可去除该属性,也就是 `P extends keyof A & keyof B ? never : P`。讲到这里题解基本就出来了。
3030

@@ -41,4 +41,4 @@ type Diff<O, O1> = {
4141

4242
## 💡 知识点
4343

44-
1.[实现 Omit](/medium/实现Omit.md)
44+
1.[实现 Omit](./Omit.md)

docs/challenges/medium/Flatten.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type Flatten<T> =
3838

3939
这里可以注意下 `[...Flatten<F>, ...Flatten<R>]`,因为既需要递归第一个元素,也需要递归剩余元素,还是有一定的圈圈绕。
4040

41-
同时可以留意下最后的 空元组,不理解的可以查看 [实现 Pop](/medium/实现Pop.md) 中提到的边界条件。
41+
同时可以留意下最后的 空元组,不理解的可以查看 [实现 Pop](./Pop.md) 中提到的边界条件。
4242

4343
## 💡 知识点
4444

docs/challenges/medium/Merge.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type Merge<T, S> = {
3838
} & S;
3939
```
4040

41-
这是一种思路,不借助 Exclude 本身也能实现,就是借助 [实现 Omit](/medium/实现Omit.md) 来完成,代码如下:
41+
这是一种思路,不借助 Exclude 本身也能实现,就是借助 [实现 Omit](./Omit.md) 来完成,代码如下:
4242

4343
```ts
4444
type Merge<T, S> = {
@@ -67,4 +67,4 @@ type Merge<F, S> = {
6767

6868
## 💡 知识点
6969

70-
1.[实现 Omit](/medium/实现Omit.md)
70+
1.[实现 Omit](./Omit.md)

docs/challenges/medium/Pop.md

Lines changed: 0 additions & 45 deletions
This file was deleted.

docs/challenges/medium/Promise.all.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const p = PromiseAll([promise1, promise2, promise3] as const);
2121

2222
## 🔍 分析
2323

24-
这一题可以遍历整个元组,并将元组中的每一个元素通过之前实现的 [Awaited](/easy/实现Awaited.md) 转换一下即可。
24+
这一题可以遍历整个元组,并将元组中的每一个元素通过之前实现的 [Awaited](../easy/awaited.md) 转换一下即可。
2525

2626
元组的遍历基本上有三种方式:
2727

@@ -100,4 +100,4 @@ declare function PromiseAll<T extends any[]>(
100100
## 💡 知识点
101101

102102
1. ts 类型推断时,会尝试计算一个更通用的类型,比如 `[1, 2, 3]` 就会被推断成 `number[]`。这里要注意此处的类型推断是指的 ts 中的隐式类型推断,和 `A extends infer xxx` 还是不一样的。这种隐式类型推断一般发生在 `const a = [1, 2, 3] -> a = number[]` 以及函数的入参中
103-
2.[Awaited](/easy/实现Awaited.md)
103+
2.[Awaited](../easy/awaited.md)

docs/challenges/medium/ReplaceAll.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type replaced = ReplaceAll<'t y p e s', ' ', ''>; // 期望是 'types'
1616

1717
## 🔍 分析
1818

19-
这一题是上一题 [实现 replace](/medium/实现Replace.md) 的升级版,不仅仅需要匹配第一次,还需要匹配剩余字符中的符合条件的字符。不过整体还是非常简单的,只需要通过递归嵌套即可。
19+
这一题是上一题 [实现 replace](./Replace.md) 的升级版,不仅仅需要匹配第一次,还需要匹配剩余字符中的符合条件的字符。不过整体还是非常简单的,只需要通过递归嵌套即可。
2020

2121
## 🛠️ 题解
2222

docs/challenges/medium/Trim.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,4 @@ type Trim<S extends string> = S extends
4848

4949
## 💡 知识点
5050

51-
1.[TirmLeft](/medium/实现TrimLeft.md)
51+
1.[TirmLeft](./TrimLeft.md)

0 commit comments

Comments
 (0)