/**
@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)
反转入参,正好用到上一题写的Reverse
,infer
推导一下即可:
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]
}