引言
TS 是 JavaScript 的超集!那 TS 可以实现类似数组的方法吗?今天我们就来探索 TS 那些有趣的玩法!
pop、push、shift、unshift
下面我们会用到一些 TS 高级语法,有不懂的可以先去看 TS 文档传送门。让我们先从简单的着手~
pop
js
type Pop<T extends any[]> = T extends [...infer F, L] ? L : never
// 让我们测试一下
type p1 = Pop<[1,2,3,4,5]> // 5
type p2 = Pop<['hello']> // hello
这儿用到了 extends(TS2.8版本) 语法、infer(TS2.8版本) 语法、...(TS3.0版本)语法,后面会大量用到,不懂得可以先去做了解。
push
js
type Push<T extends any[], V> = V extends any[] ? [...T, ...V] : [...T, V]
type p2 = Push<[1,2,3,4,5], ['hello', 'world']> // [1,2,3,4,5,hello,world]
type p2 = Push<[1,2,3,4,5], ['hello', ['world']]> // [1,2,3,4,5,hello,[world]]
type p4 = Push<[1,2,3,4], 5> // [1,2,3,4,5]
是不是 so easy! :)下面我们提高难度
shift、unshift 方法类似,大家可以自行尝试
reverse、flat、filter、splice、join
reverse(反转
js
type Reverse<T extends any[], R extends any[] = []> = T extends [infer F, ...infer L] ? Reverse<L, [F, ...R]> : R
type r1 = Reverse<[1,2,3,4,5> // [5,4,3,2,1]
我们采用递归思想(下面基本都会用到递归,同学们食好瓜子er),先取出第一个值,然后递归放入我们的结果数组中,注意最后[F, ...R],别放反了!
flat(拍平,一马平川
js
type Flat<T extends any[]> = T extends [infer F, ...infer S] ? [...(F extends any[] ? Flat<F> : [F]), ...Flat<S>] : T
type f1 = Flat<[[[4]]], 5]> // [4, 5]
没办法,继续递归~
filter(过滤
做之前仔细想一下,这里面是要作比较,判断两个类型是否一致,一致就过滤出来。那我们怎么判断呢?
js
// 这段代码有点啰嗦,各位同学可以试着精简一下
type Filter<
T extends any[],
V,
R extends any[] = []
> = T extends [infer F, ...infer S]
? F extends V
? Filter<S, V, [...R, F]>
: Filter<S, V, R>
: R
type f1 = Filter<[1, 'bef', 2, any, 'dev'], string> // ['bef', any, 'dev'] | ['bef', 'dev']
我们发现上面的代码,产生了两种结果,这是为什么?(别问我,我也木鸡啊:)
其实这是 TS 类型分发问题,原理很简单,举个栗子
type a = any extends string ? 1 : 2,你会发现结果是联合类型 1 | 2,仔细想下很简单,any可以是任何东西,可以是 number,可以是 string ,那 number 是 string 类型吗?显然不是,返回 2 ,那 string 是 string 类型吗?显然是,返回 1 ,一联合就是 1 | 2。 原理我们知道了,怎么规避呢?在了解了什么情况下会产生类型分发,就很好解决了。只要它不是裸类型 (当然,字面量 作比较,是不会分发的),就不会分发了,我们可以在 F extends V 包一层,[F] extends [V] 就OK了
splice(删除、追加、替换
js
type Splice<
T extends any[],
S extends number, // 开始删除的索引
C extends number = T['length'], // 删除的个数
IN extends any[] = [], // 需要替换的值
SA extends any[] = [], // 用于计算开始的位置
EA extends any[] = [], // 用于计算删除的个数
F extends any[] = [] // 前面需要保留的值
> = T extends [infer L, ...infer R]
? SA['length'] extends S
? EA['length'] extends C
? [...F, ...IN, L, ...R] // [...F, ...IN, ...T]
: Splice<R, S, C, IN, SA, [...EA, never], F>
: Splice<R, S, C, IN, [...SA, never], EA, [...F, L]>
: [...F, ...IN]
type a1 = Splice<[boolean, 1, 'a', never, string], 1, 2, [3, 3]> // [boolean, 3, 3, never, string]
type a2 = Splice<[boolean, 'never', 1, 2, string], 0> // []
type a3 = Splice<[boolean, any, number, 2, 'a'], 1, 1> // [boolean, number, 2, 'a']
type a4 = Splice<[boolean, any, number, 2, 'a'], 4, 0, [99]> // [boolean, any, number, 2, 99, 'a']
注意看第二个栗子(a2),删除的个数,默认删除开始位置到数组最后一项。在最后的结果中,须将当前的 L 放进去(就因为这个点,当初迷惑了我一下午:)
join(拼接
js
type Join<
T extends any[],
R extends string = ''
> = T extends [infer F, ...infer S]
? S['length'] extends 0
? `${R}${F}`
: Join<S, `${R}${F}`>
: ''
type s = Join<['h', 'e', 'l', 'l', 'o']> // hello
总结
上面介绍了 TS 与数组的不解之缘,可谓有趣的很。还有很多有趣的玩法,这里就不一一列举了。除了数组,还有字符串。有时间可以跟大家一起探讨~