likes
comments
collection
share

TS与数组的那些事儿😏

作者站长头像
站长
· 阅读数 13

引言

TS 是 JavaScript 的超集!那 TS 可以实现类似数组的方法吗?今天我们就来探索 TS 那些有趣的玩法!

pop、push、shift、unshift

下面我们会用到一些 TS 高级语法,有不懂的可以先去看 TS 文档传送门。让我们先从简单的着手~

pop

    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

    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(反转

    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(拍平,一马平川

    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(过滤

做之前仔细想一下,这里面是要作比较,判断两个类型是否一致,一致就过滤出来。那我们怎么判断呢?

    // 这段代码有点啰嗦,各位同学可以试着精简一下
    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(删除、追加、替换

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(拼接

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 与数组的不解之缘,可谓有趣的很。还有很多有趣的玩法,这里就不一一列举了。除了数组,还有字符串。有时间可以跟大家一起探讨~