更强大的使用TypeScript(4)

219 阅读5分钟
    /**
        @date 2021-02-19
        @description ts-type-challenge-中等题目完
    */

壹(序)

目前为止,已经做完了绝大部分中等题目,有少数的几个实在做不来,也没看明白别人的答案,就放弃了,接下来会尝试困难难度的题目,如果太耗时的话可能会终止此系列,去看看其他的东西。

贰(TupleToNestedObject)

要根据 tuple 返回嵌套对象的话,毫无疑问需要递归,那就对数组一项项的处理,在数组没值时跳出递归即可,所以大概能得到:

type TupleToNestedObject<T, U> = T extends [infer Start, ...infer Last] ? { [K in Start]: TupleToNestedObject<Last, U> } : U

不过这样是会报错的,因为对象的属性名需要string | number | symbol,那么使用断言?也不行,as会让结果变成[x: string]: {}的结构,应该使用&来处理这个报错:

type TupleToNestedObject<T, U> = T extends [infer Start, ...infer Last] ? { [K in Start & string]: TupleToNestedObject<Last, U> } : U

叁(Reverse)

很简单,自定义一个R,初始为空数组,然后一项一项的往R塞就行了:

type Reverse<T, R extends any[] = []> = T extends [infer Start, ...infer Last] ? Reverse<Last, [Start, ...R]> : R

肆(FlipArguments)

反转入参,正好用到上一题写的Reverseinfer推导一下即可:

type FlipArguments<T> = T extends (...args: infer A) => infer R ? (...args: Reverse<A>) => R : T

伍(FlattenDepth)

想要根据特定的深度flatten,肯定需要传入一个depth,就像js实现flattenDepth一样,但是ts并不支持减法,所以还需要定义一个数组,里面传什么都行,用这个数组的length来与depth比较,从而判断当前是否是需要跳出循环,所以大概得到:

type FlattenDepth<List, Depth = 1, Times extends any[] = []> =
  Times['length'] extends Depth
  ? List
  : FlattenDepth<FlattenOnce<List>, Depth, [...Times, any]>

但是这样会遇到之前没做出来的一道题MinusOne遇到的问题:Type instantiation is excessively deep and possibly infinite.;

这是ts做的限制,在递归999次以上就会报这个错,所以如果depth传入1000的话,就得不到正确答案了;

这道题与MinusOne遇到的是一样的问题,但是这里我们可以在已经无法继续flat时提前跳出去,并不需要真的去处理1000次,所以加一层判断:List extends FlattenOnce<List>,如果当前List与flatten一次之后是一样的,说明已经到头了,就可以提前结束:

type FlattenOnce<List> = List extends [infer Start, ...infer Last] ? Start extends any[] ? [...Start, ...FlattenOnce<Last>] : [Start, ...FlattenOnce<Last>] : []

type FlattenDepth<List, Depth = 1, Times extends any[] = []> =
  Times['length'] extends Depth
  ? List
  : List extends FlattenOnce<List>
  ? List
  : FlattenDepth<FlattenOnce<List>, Depth, [...Times, any]>

陆(BEM)

对于想要将数组T转为联合类型,直接取T[number]就行了,比如:

type Test<T extends string[]> = T[number];

type d = Test<['1', '2']>; // '1' | '2'

所以这里很简单,两个数组处理两次即可,但是需注意空数组的情况:

type BEM<B extends string, E extends string[], M extends string[]> = `${B}${E extends [] ? '' : `__${E[number]}`}${M extends [] ? '' : `--${M[number]}`}`

柒(Flip)

无非就是将属性值与属性名调换,对属性值用as处理即可:

type Flip<T> = {
  [K in keyof T as T[K]]: K
}

不过会有报错,因为对象属性名只能是string | number | symbol,而遇到属性值为boolean的话就无法直接使用,所以需要特殊处理:

type Flip<T> = {
  [K in keyof T as T[K] extends string | number | symbol ? T[K] : T[K] extends boolean ? `${T[K]}` : never]: K
}

捌(GreaterThan)

比较一个数字是否大于另一个数字,涉及到数字的比较,一般会使用一个any数组,取这个数组的length来处理,所以这里自定义一个数组A,一开始A的length为0,随着递归会递增,跳出递归的条件就是A的length与T或U相等了,这个时候说明已经得到结果了,如果先走到T与length相等,说明T较小,同理,走到U与length相等说明T较大;

type GreaterThan<T extends number, U extends number, A extends any[] = []> =
  T extends A['length']
  ? false
  : U extends A['length']
  ? true
  : GreaterThan<T, U, [...A, any]>

玖(Zip)

这题比较简单,一项一项的处理即可:

type Zip<T extends any[], U extends any[], R extends any[] = []> =
  T extends [infer S1, ...infer L1]
  ? U extends [infer S2, ...infer L2]
  ? Zip<L1, L2, [...R, [S1, S2]]>
  : R
  : R

拾(IsTuple)

判断是否是tuple,直接判断是否为数组即可,不过还要增加readonly的情况,还有一点就是注意number[]的情况,这里判断T['length']是否为number即可,因为tuple的length是常量number;

type IsTuple<T> = T extends any[] | readonly any[]
  ? number extends T['length']
  ? false
  : true
  : false

拾壹(Chunk)

首先肯定需要递归,然后对数组的每一项进行处理,这就需要定义一个数组U,来保存每一项,以及一个数组R来返回最终结果,并且在U的length等于N时,将U插入R,并清空U来继续处理剩下的数据:

type Chunk<T extends any[], N extends number, U extends any[] = [], R extends any[] = []> =
  T extends [infer S, ...infer L]
  ? U['length'] extends N
  ? Chunk<T, N, [], [...R, U]>
  : Chunk<L, N, [...U, S], R>
  : U extends []
  ? R
  : [...R, U]

拾贰(TrimRight)、

之前有Trim记忆TrimLeft,用infer推断即可:

type TrimRight<S extends string> = S extends `${infer Start}${' ' | '\n' | '\t'}` ? TrimRight<Start> : S

拾叁(Without)

首先需要一个Includes,来判断数组中是否包含某项,然后就是递归处理了:

type Includes<T extends readonly any[], U> = T extends [infer F, ...infer R]
  ? (Equal<F, U> extends true ? true : Includes<R, U>)
  : false

type Without<T extends any[], U extends any[] | any, R extends any[] = []> =
  T extends [infer F, ...infer L]
  ? Includes<U extends any[] ? U : [U], F> extends true
  ? Without<L, U extends any[] ? U : [U], R>
  : Without<L, U extends any[] ? U : [U], [...R, F]>
  : R

拾肆(Trunc)

使用infer推断即可:

type Trunc<N extends string | number> = `${N}` extends `${infer R}.${infer L}` ? R : `${N}`

拾伍(IndexOf)

以之前的经验,需要得到一个数字的,都需要自定义一个数组,然后取这个数组的length,一项一项的去判断,相等了返回length即可:

type IndexOf<T extends any[], U extends number, R extends any[] = []> =
  T extends [infer F, ...infer L]
  ? F extends U
  ? R['length']
  : IndexOf<L, U, [...R, 1]>
  : -1

拾陆(Join)

一项一项的处理,取到最后一项时不加上U即可:

type Join<T extends string[], U extends string | number, R extends string = ''> =
  T extends [infer F, ...infer L]
  ? L['length'] extends 0
  ? `${R}${F}`
  : Join<L, U, `${R}${F}${U}`>
  : R

拾柒(LastIndexOf)

同上IndexOf:

type LastIndexOf<T extends any[], U extends number> =
  T extends [...infer F, infer L]
  ? L extends U
  ? F['length']
  : LastIndexOf<F, U>
  : -1

拾捌(Unique)

同样的,也需要Includes,再自定义一个数组R,然后一项一项的处理,在当前项被R包含时,不处理此项,不包含则插入进R中,

type Includes<T extends readonly any[], U> = T extends [infer F, ...infer R]
  ? (Equal<F, U> extends true ? true : Includes<R, U>)
  : false

type Unique<T extends any[], R extends any[] = []> =
  T extends [infer F, ...infer L]
  ? Includes<R, F> extends true
  ? Unique<L, R>
  : Unique<L, [...R, F]>
  : R

拾玖(MapTypes)

比较简单,跟着题意一步步判断即可:

type MapTypes<T, R extends { mapFrom: any, mapTo: any }> = {
  [K in keyof T]: T[K] extends R['mapFrom'] ? R extends { mapFrom: T[K] } ? R['mapTo'] : never : T[K]
}