当前期刊数: 251
解决 TS 问题的最好办法就是多练,这次解读 type-challenges Medium 难度 57~62 题。
精读
实现 TrimRight
删除右侧空格:
1
| type Trimed = TrimRight<' Hello World '>
|
用 infer
找出空格前的字符串递归一下即可:
1 2 3
| type TrimRight<S extends string> = S extends `${infer R}${' '}` ? TrimRight<R> : S
|
再补上测试用例的边界情况,\n
与 \t
后就是完整答案了:
1 2 3 4
| type TrimRight<S extends string> = S extends `${infer R}${' ' | '\n' | '\t'}` ? TrimRight<R> : S
|
实现 Without<T, U>
,从数组 T
中移除 U
中元素:
1 2 3
| type Res = Without<[1, 2], 1> type Res1 = Without<[1, 2, 4, 1, 5], [1, 2]> type Res2 = Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]>
|
该题最难的点在于,参数 U
可能是字符串或字符串数组,我们要判断是否存在只能用 extends
,这样就存在两个问题:
- 既是字符串又是数组如何判断,合在一起判断还是分开判断?
[1] extends [1, 2]
为假,数组模式如何判断?
可以用数组转 Union 的方式解决该问题:
1
| type ToUnion<T> = T extends any[] ? T[number] : T
|
这样无论是数字还是数组,都会转成联合类型,而联合类型很方便判断 extends
包含关系:
1 2 3 4 5 6
| type Without<T, U> = T extends [infer H, ...infer R] ? H extends ToUnion<U> ? Without<R, U> : [H, ...Without<R, U>] : []
|
每次取数组第一项,判断是否被 U
包含,是的话就丢弃(丢弃的动作是把 H
抛弃继续递归),否则包含(包含的动作是形成新的数组 [H, ...]
并把递归内容解构塞到后面)。
实现 Math.trunc
相同功能的函数 Trunc
:
如果入参是字符串就很简单了:
1
| type Trunc<T> = T extends `${infer H}.${infer R}` ? H : ''
|
如果不是字符串,将其转换为字符串即可:
1 2 3 4
| type Trunc<T extends string | number> = `${T}` extends `${infer H}.${infer R}` ? H : `${T}`
|
实现 IndexOf
寻找元素所在下标,找不到返回 -1
:
1 2 3
| type Res = IndexOf<[1, 2, 3], 2>; type Res1 = IndexOf<[2,6, 3,8,4,1,7, 3,9], 3>; type Res2 = IndexOf<[0, 0, 0], 2>;
|
需要用一个辅助变量存储命中下标,递归的方式一个个判断是否匹配:
1 2 3 4 5 6
| 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
函数判断相等:
1 2 3 4 5 6 7
| 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
|
实现 TS 版 Join<T, P>
:
1 2 3 4
| type Res = Join<["a", "p", "p", "l", "e"], "-">; type Res1 = Join<["Hello", "World"], " ">; type Res2 = Join<["2", "2", "2"], 1>; type Res3 = Join<["o"], "u">;
|
递归 T
每次拿第一个元素,再使用一个辅助字符串存储答案,拼接起来即可:
1 2 3 4 5 6 7
| 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
寻找最后一个匹配的下标:
1 2
| type Res1 = LastIndexOf<[1, 2, 3, 2, 1], 2> type Res2 = LastIndexOf<[0, 0, 0], 2>
|
和 IndexOf
类似,从最后一个下标往前判断即可。需要注意的是,我们无法用常规办法把 Index
下标减一,但好在 R
数组长度可以代替当前下标:
1 2 3 4 5 6
| 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
如果你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。
关注 前端精读微信公众号
版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证)