type a = FlattenDepth<[1, 2, [3, 4], [[[5]]]], 2> // [1, 2, 3, 4, [5]]. 深度为2,拍平两层 type b = FlattenDepth<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, [[5]]]. 默认拍平深度为 1如果提供了深度,则保证为正整数。
import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<FlattenDepth<[]>, []>>, Expect<Equal<FlattenDepth<[1, 2, 3, 4]>, [1, 2, 3, 4]>>, Expect<Equal<FlattenDepth<[1, [2]]>, [1, 2]>>, Expect<Equal<FlattenDepth<[1, 2, [3, 4], [[[5]]]], 2>, [1, 2, 3, 4, [5]]>>, Expect<Equal<FlattenDepth<[1, 2, [3, 4], [[[5]]]]>, [1, 2, 3, 4, [[5]]]>>, Expect<Equal<FlattenDepth<[1, [2, [3, [4, [5]]]]], 3>, [1, 2, 3, 4, [5]]>>, Expect<Equal<FlattenDepth<[1, [2, [3, [4, [5]]]]], 19260817>, [1, 2, 3, 4, 5]>>, ]思路
type FlattenDepth< T extends any[], Dept = 1, Result extends any[] = [] > = T extends [infer F, ...infer R] ? FlattenDepth<R, Dept, [...Result, ...(F extends any[] ? F : [F])]> : Result第一步的处理很简单,就是遍历数组的每个元素,如果当前元素是数组,则使用扩展运算符展开,否则,不处理;这就实现了简单地把目标数组拍平一层。如 [1, [2]] 会被处理成 [1, 2]。
// 如果元组的长度为 2,则返回 type Arr1 = [1] type Arr2 = [1, 2] type Magic<T extends any[]> = T['length'] extends 2 ? T : never type Result1 = Magic<Arr1> // never type Result2 = Magic<Arr2> // [1, 2]所以,我们可以通过声明一个泛型 DeptArr ,专门用来记录当前的深度;每进行一次拍平,使目标类型的深度减 1,则在 DeptArr 中添加一个元素,此时它的长度就增加了 1,这样我们在递归中就能判断当前拍平了多少层了。
type FlattenDepth< T extends any[], Dept = 1, DeptArr extends any[] = [], Result extends any[] = [] > = T extends [infer F, ...infer R] ? FlattenDepth<R, Dept, DeptArr, [...Result, ...(F extends any[] ? F : [F])]> : [...DeptArr, 1]['length'] extends Dept ? Result : FlattenDepth<Result, Dept, [...DeptArr, 1]>乍一看,从前面的思路出发,好像也没什么问题。但是当我们观察测试 Cases 的时候,会发现最后一个 Case 飘红,报了如下错误:
Type instantiation is excessively deep and possibly infinite.在一些情况下,当递归 / 循环深度过大的时候,Typescript 会觉得我们的代码陷入了无限的递归,于是给我们抛出以上的错误。归根结底,是因为最后一个 Case 中的拍平深度 19260817 过大;据观察,深度超过 167 就会报错(不确定是不是不同版本的 Typescript 的限制不一样) 。
type FlattenDepth< T extends any[], Dept = 1, DeptArr extends any[] = [], Result extends any[] = [] > = T extends [infer F, ...infer R] ? FlattenDepth<R, Dept, DeptArr, [...Result, ...(F extends any[] ? F : [F])]> : [...DeptArr, 1]['length'] extends Dept ? Result : Result[number] extends number ? Result : FlattenDepth<Result, Dept, [...DeptArr, 1]>总结
通过这个题目,我们可以发现,在 Tyepscript 中进行递归时,有可能会导致一些奇怪的问题,虽说这个题目中的使用场景,在日常开发中是遇不上的,但是也有可能某天在封装一些复杂组件时,就遇上了,所以要留个心眼。