Skip to content

Commit

Permalink
251
Browse files Browse the repository at this point in the history
  • Loading branch information
ascoders committed Aug 1, 2022
1 parent 8d3fc00 commit e36a29c
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 1 deletion.
177 changes: 177 additions & 0 deletions TS 类型体操/251.精读《Trim Right, Without, Trunc...》.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
解决 TS 问题的最好办法就是多练,这次解读 [type-challenges](https://github.com/type-challenges/type-challenges) Medium 难度 57~62 题。

## 精读

### [Trim Right](https://github.com/type-challenges/type-challenges/blob/main/questions/04803-medium-trim-right/README.md)

实现 `TrimRight` 删除右侧空格:

```ts
type Trimed = TrimRight<' Hello World '> // expected to be ' Hello World'
```
`infer` 找出空格前的字符串递归一下即可:
```ts
type TrimRight<S extends string> = S extends `${infer R}${' '}`
? TrimRight<R>
: S
```
再补上测试用例的边界情况,`\n``\t` 后就是完整答案了:
```ts
// 本题答案
type TrimRight<S extends string> = S extends `${infer R}${' ' | '\n' | '\t'}`
? TrimRight<R>
: S
```
### [Without](https://github.com/type-challenges/type-challenges/blob/main/questions/05117-medium-without/README.md)
实现 `Without<T, U>`,从数组 `T` 中移除 `U` 中元素:
```ts
type Res = Without<[1, 2], 1> // expected to be [2]
type Res1 = Without<[1, 2, 4, 1, 5], [1, 2]> // expected to be [4, 5]
type Res2 = Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]> // expected to be []
```
该题最难的点在于,参数 `U` 可能是字符串或字符串数组,我们要判断是否存在只能用 `extends`,这样就存在两个问题:
1. 既是字符串又是数组如何判断,合在一起判断还是分开判断?
2. `[1] extends [1, 2]` 为假,数组模式如何判断?
可以用数组转 Union 的方式解决该问题:
```ts
type ToUnion<T> = T extends any[] ? T[number] : T
```
这样无论是数字还是数组,都会转成联合类型,而联合类型很方便判断 `extends` 包含关系:
```ts
// 本题答案
type Without<T, U> = T extends [infer H, ...infer R]
? H extends ToUnion<U>
? Without<R, U>
: [H, ...Without<R, U>]
: []
```
每次取数组第一项,判断是否被 `U` 包含,是的话就丢弃(丢弃的动作是把 `H` 抛弃继续递归),否则包含(包含的动作是形成新的数组 `[H, ...]` 并把递归内容解构塞到后面)。
### [Trunc](https://github.com/type-challenges/type-challenges/blob/main/questions/05140-medium-trunc/README.md)
实现 `Math.trunc` 相同功能的函数 `Trunc`:
```ts
type A = Trunc<12.34> // 12
```
如果入参是字符串就很简单了:
```ts
type Trunc<T> = T extends `${infer H}.${infer R}` ? H : ''
```
如果不是字符串,将其转换为字符串即可:
```ts
// 本题答案
type Trunc<T extends string | number> = `${T}` extends `${infer H}.${infer R}`
? H
: `${T}`
```
### [IndexOf](https://github.com/type-challenges/type-challenges/blob/main/questions/05153-medium-indexof/README.md)
实现 `IndexOf` 寻找元素所在下标,找不到返回 `-1`
```ts
type Res = IndexOf<[1, 2, 3], 2>; // expected to be 1
type Res1 = IndexOf<[2,6, 3,8,4,1,7, 3,9], 3>; // expected to be 2
type Res2 = IndexOf<[0, 0, 0], 2>; // expected to be -1
```

需要用一个辅助变量存储命中下标,递归的方式一个个判断是否匹配:

```ts
type IndexOf<T, U, Index extends any[] = []> =
T extends [infer F, ...infer R]
? F extends U
? Index['length']
: IndexOf<R, U, [...Index, 0]>
: -1
```
但没有通过测试用例 `IndexOf<[string, 1, number, 'a'], number>`,原因是 `1 extends number` 结果为真,所以我们要换成 `Equal` 函数判断相等:
```ts
// 本题答案
type IndexOf<T, U, Index extends any[] = []> =
T extends [infer F, ...infer R]
? Equal<F, U> extends true
? Index['length']
: IndexOf<R, U, [...Index, 0]>
: -1
```
### [Join](https://github.com/type-challenges/type-challenges/blob/main/questions/05310-medium-join/README.md)
实现 TS 版 `Join<T, P>`:
```ts
type Res = Join<["a", "p", "p", "l", "e"], "-">; // expected to be 'a-p-p-l-e'
type Res1 = Join<["Hello", "World"], " ">; // expected to be 'Hello World'
type Res2 = Join<["2", "2", "2"], 1>; // expected to be '21212'
type Res3 = Join<["o"], "u">; // expected to be 'o'
```
递归 `T` 每次拿第一个元素,再使用一个辅助字符串存储答案,拼接起来即可:
```ts
// 本题答案
type Join<T, U extends string | number> =
T extends [infer F extends string, ...infer R extends string[]]
? R['length'] extends 0
? F
: `${F}${U}${Join<R, U>}`
: ''
```
唯一要注意的是处理到最后一项时,不要再追加 `U` 了,可以通过 `R['length'] extends 0` 来判断。
### [LastIndexOf](https://github.com/type-challenges/type-challenges/blob/main/questions/05317-medium-lastindexof/README.md)
实现 `LastIndexOf` 寻找最后一个匹配的下标:
```ts
type Res1 = LastIndexOf<[1, 2, 3, 2, 1], 2> // 3
type Res2 = LastIndexOf<[0, 0, 0], 2> // -1
```
`IndexOf` 类似,从最后一个下标往前判断即可。需要注意的是,我们无法用常规办法把 `Index` 下标减一,但好在 `R` 数组长度可以代替当前下标:
```ts
// 本题答案
type LastIndexOf<T, U> = T extends [...infer R, infer L]
? Equal<L, U> extends true
? R['length']
: LastIndexOf<R, U>
: -1
```
## 总结
本周六道题都没有刷到新知识点,中等难题还剩 6 道,如果学到这里能有种索然无味的感觉,说明前面学习的很扎实。
> 讨论地址是:[精读《Trim Right, Without, Trunc...》· Issue #433 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/433)
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
> 关注 **前端精读微信公众号**
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
3 changes: 2 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

前端界的好文精读,每周更新!

最新精读:<a href="./TS 类型体操/250.%E7%B2%BE%E8%AF%BB%E3%80%8AFlip%2C%20Fibonacci%2C%20AllCombinations...%E3%80%8B.md">250.精读《Flip, Fibonacci, AllCombinations...》</a>
最新精读:<a href="./TS 类型体操/251.%E7%B2%BE%E8%AF%BB%E3%80%8ATrim%20Right%2C%20Without%2C%20Trunc...%E3%80%8B.md">251.精读《Trim Right, Without, Trunc...》</a>

素材来源:[周刊参考池](https://github.com/ascoders/weekly/issues/2)

Expand Down Expand Up @@ -202,6 +202,7 @@
- <a href="./TS 类型体操/248.%E7%B2%BE%E8%AF%BB%E3%80%8AMinusOne%2C%20PickByType%2C%20StartsWith...%E3%80%8B.md">248.精读《MinusOne, PickByType, StartsWith...》</a>
- <a href="./TS 类型体操/249.%E7%B2%BE%E8%AF%BB%E3%80%8AObjectEntries%2C%20Shift%2C%20Reverse...%E3%80%8B.md">249.精读《ObjectEntries, Shift, Reverse...》</a>
- <a href="./TS 类型体操/250.%E7%B2%BE%E8%AF%BB%E3%80%8AFlip%2C%20Fibonacci%2C%20AllCombinations...%E3%80%8B.md">250.精读《Flip, Fibonacci, AllCombinations...》</a>
- <a href="./TS 类型体操/251.%E7%B2%BE%E8%AF%BB%E3%80%8ATrim%20Right%2C%20Without%2C%20Trunc...%E3%80%8B.md">251.精读《Trim Right, Without, Trunc...》</a>

### 设计模式

Expand Down

0 comments on commit e36a29c

Please sign in to comment.