TS类型技巧(三):递归
应对数量不确定的情况时, 需要使用 递归
1. 提取Promise value类型
type ttt = Promise<Promise<Promise<number>>>
type DeepPromiseValueType<T> = T extends Promise<infer U>
? DeepPromiseValueType<U>
: T
// number
type ttt2 = DeepPromiseValueType<ttt>
2. 反转数组
type ReverseArr<Arr extends unknown[]> = Arr extends [
infer First,
...infer Rest
]
? [...ReverseArr<Rest>, First]
: Arr
// [5,4,3,2,1]
type arr2 = ReverseArr<arr>
注意泛型不能写成 type ReverseArray<T: any[]>, :是函数的语法
3. 查找数组元素
type Includes<
Arr extends unknown[],
FindItem
> = Arr extends [infer First, ...infer Rest]
? IsEqual<First, FindItem> extends true
? true
: Includes<Rest, FindItem>
: false
type IsEqual<A, B> = (A extends B ? true : false) &
(B extends A ? true : false)
type arr3 = [1, 2, 3, 4, 5]
type res = Includes<arr3, 3> // true
type res2 = Includes<arr3, 6> // false
4. 删除数组元素
type RemoveItem<
T extends unknown[],
DeleteItem,
Result extends unknown[] = []
> =
// 不管相不相等都继续递归 RemoveItem, 直到数组为空返回Result, 做到全部删除
T extends [infer First, ...infer Rest]
? IsEqual<First, DeleteItem> extends true
? // 如果相等就不放进Result,
RemoveItem<Rest, DeleteItem, Result>
: // 如果不相等,就放进Result, 注意...Result在前面
RemoveItem< Rest, DeleteItem, [...Result, First] >
: Result
type IsEqual<A, B> = (A extends B ? true : false) &
(B extends A ? true : false)
type arr5 = [1, 2, 3, 3, 4, 5, 3]
type res4 = RemoveItem<arr5, 3> // [1,2,4,5]
5. 扁平化数组
// 两层
type Flatten<
Arr extends unknown[],
Result extends unknown[] = []
> = Arr extends [infer First, ...infer Rest]
? First extends unknown[]
? Flatten<Rest, [...Result, ...First]>
: Flatten<Rest, [...Result, First]>
: Result
// 多层
type DeepFlatten<
Arr extends unknown[],
Result extends unknown[] = []
> = Arr extends [infer First, ...infer Rest]
? First extends unknown[]
? Flatten<[...First, ...Rest], Result>
: Flatten<Rest, [...Result, First]>
: Result
6. 构造数组
每次判断下 Arr 的长度是否到了 Length, 是的话就返回 Arr, 否则在 Arr 上加一个元素, 然后递归构造
type BuildArray<
Length extends number,
Ele = unknown,
R extends unknown[] = []
> = R['length'] extends Length
? R
: BuildArray<Length, Ele, [...R, Ele]>
// [string, string, string, string, string]
type a = BuildArray<5, string>
技巧! 积累参数(accumulator)
在4.中使用了 带累加器的尾递归写法
积累参数(accumulator)+ 尾递归(tail recursion)
具有更好的编译性能, 更受开源库欢迎
不使用累加器, 递归发生在最后一步, 堆栈的“回溯阶段”更重, 而且生成的类型树更大
// 非累加器写法
type RemoveItem<
T extends unknown[],
DeleteItem
> = T extends [infer First, ...infer Rest]
? IsEqual<First, DeleteItem> extends true
? RemoveItem<Rest, DeleteItem>
: [First, ...RemoveItem<Rest, DeleteItem>]
: []
// 累加器写法
type RemoveItem2<
T extends unknown[],
DeleteItem,
Result extends unknown[] = []
> = T extends [infer First, ...infer Rest]
? IsEqual<First, DeleteItem> extends true
? RemoveItem2<Rest, DeleteItem, Result>
: RemoveItem2<Rest, DeleteItem, [...Result, First]>
: Result
7. 字符串递归
ReplaceStrAll
type ReplaceStrAll<
Str extends string,
From extends string,
To extends string
> = Str extends `${infer Prefix}${From}${infer Suffix}`
? `${Prefix}${To}${ReplaceStrAll<Suffix, From, To>}`
: Str
// 'hello typescript typescript'
type f = ReplaceStrAll<'hello world world', 'world', 'typescript'>
StringToUnion
type StringToUnion<T extends string> =
T extends `${infer C}${infer Rest}`
? C | StringToUnion<Rest>
: never
// 'h' | 'e' | 'l' | 'o'
type a = StringToUnion<'hello'>
ReverseStr
type ReverseStr<
T extends string,
Result extends string = ''
> = T extends `${infer First}${infer Rest}`
? ReverseStr<Rest, `${First}${Result}`>
: Result
type c = ReverseStr<'tenet'> // 'tenet'
8. 对象类型的递归
DeepReadonly
type DeepReadonly<Obj extends Record<string, any>> = {
readonly [Key in keyof Obj]: Obj[Key] extends object
? Obj[Key] extends Function
? Obj[Key]
: DeepReadonly<Obj[Key]>
: Obj[Key]
}
type DeepReadonly2<Obj extends Record<string, any>> =
Obj extends any
? {
readonly [Key in keyof Obj]: Obj[Key] extends object
? Obj[Key] extends Function
? Obj[Key]
: DeepReadonly2<Obj[Key]>
: Obj[Key]
}
: never
type obj = { a: { b: { c: string } } }
// {readonly a: DeepReadonly<{b:{c: string}}>}
type obj1 = DeepReadonly<obj>
// {readonly a: {readonly b: {readonly c: string}}}
type obj2 = DeepReadonly2<obj>
Obj extends any ? { ... } : never 是 借用条件类型引发 强制计算/归一化 的技巧
技巧!强制计算/归一化
延迟推导(Deferred Type Resolution)
type obj1 = {readonly a: DeepReadonly<{b: { c: string}}>}
非归一化的类型, TS 默认类型推导是惰性的
一个类型别名或泛型引用,在没有访问其属性或结构的上下文中时,不会被立即展开。
只有当上下文 需要 知道类型的具体结构时才会展开,例如a.b.c
归一化(Normalized)
type obj2 = {readonly a: {readonly b: {readonly c: string}}}
归一化类型(Normalized Type), 即表现为最终结构的类型
解开了所有类型别名, 条件类型, 类型推导, 并合并了结果
条件类型的计算 具有优先性 / 触发强制计算 / 触发归一化, 导致当前层被展开
每一层的 Obj extends any ? ... : never 都会触发 分布式条件类型的计算
// 条件类型归一化/强制计算
type Distribute<T> = T extends unknown ? T : never;
// 映射类型归一化
type Simplify<T> = { [P in keyof T]: T[P] }
条件类型归一化(Distribute, ForceCompute),映射类型归一化(Simplify)