TypeScript类型体操挑战(十六)

329 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

中等

InorderTraversal

挑战要求

在线示例

type InorderTraversal<T extends TreeNode | null> =
  [T] extends [TreeNode]
  ?
  [...InorderTraversal<T['left']>, T['val'], ...InorderTraversal<T['right']>]
  :
  [];

答案参考自解答区

最重要的就是这段代码:[T] extends [TreeNode],这里这么做是为了防止发生分布行为。

一开始我是这么做的:

type InorderTraversal<T extends TreeNode | null> =
  T extends TreeNode
  ?
  /*
    这里报错:
    // 可能无穷的。。。
    Type instantiation is excessively deep and possibly infinite.(2589)
    // 这句的大概意思是产生了一个复杂的联合类型
    Expression produces a union type that is too complex to represent.(2590)
  */
  [...InorderTraversal<T['left']>, T['val'], ...InorderTraversal<T['right']>]
  :
  [];

因为T可能是个联合类型,这时使用条件类型判断会发生分布行为,然后又存在递归调用InorderTraversal,每一次调用都会产生分布行为,所以就出问题了:类型处理可能是无穷尽的,还会产生复杂的联合类型。

当时也没明白过来,然后就去解答区看了个👍多的答案:

type InorderTraversal<T extends TreeNode | null, NT extends TreeNode = NonNullable<T>> =
  T extends null
  ?
  []
  :
  [...InorderTraversal<NT['left']>, NT['val'], ...InorderTraversal<NT['right']>];

由于定义了一个新的类型NT,并且将其类型确定为TreeNode,所以后面直接使用NT来获取对应的属性类型是可行的,不会报错。

在解答区多看了几个答案后,结合一开始我所定义类型中报的错,就明白了,所以不管是哪个答案,主要问题就是确定T的类型,然后才能进行处理使用。

Flip

挑战要求

在线示例

type Flip<T extends Record<string, any>> = {
  [P in keyof T as `${T[P]}`]: P
}

参考自解答区

使用in遍历对象的键时,再通过 as 映射一个新的键,不就解决了吗?所以一开始我是这么干的:

type Flip<T> = {
  [P in keyof T as `${T[P]}`]: P
}
  
  /*
    所以报错了,在 T[P] 下面会有红色波浪线
    报错内容如下:
    Type 'T[P]' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
    Type 'T[keyof T]' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
    Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
        Type 'T[string]' is not assignable to type 'string | number | bigint | boolean | null | undefined'.(2322)
 */

一般情况下,能作为对象键的类型为stringnumbersymbol,所以通过T[P]去获取值类型是不对的,就报错了。

所以得将P的类型给确定下来,那么可以通过限制T的类型来解决:

// 使用内置工具类型 Record 来限制 T 的类型,这样键的类型就确定了
type Flip<T extends Record<string, any>> = {
  [P in keyof T as `${T[P]}`]: P
}

斐波那契序列

挑战要求

在线示例

/** 根据数值生成指定长度的元组 */
type GenerateTuple<L extends number, T extends number[] = []> =
  T['length'] extends L ? T : GenerateTuple<L, [...T, 0]>;

/** 根据当前数列,计算下一项的值 */
type Calculate<T extends number[]> =
  T extends [...any, infer A, infer B]
  ? [...GenerateTuple<A extends number ? A : never>, ...GenerateTuple<B extends number ? B : never>]['length']
  : T['length'];

type Fibonacci<T extends number, A extends number[] = [1]> = 
  A['length'] extends T 
  ? 
    A extends [...any, infer E] 
    ? E 
    : never 
  : Fibonacci<T, [...A, Calculate<A>]>;

逻辑剖析

Case 如下:

Expect<Equal<Fibonacci<3>, 2>>

1. 代入到类型当中:

type Fibonacci<3, [1]> = 
  1 extends 3		// 条件不成立 
  ? 
    A extends [...any, infer E] 
    ? E 
    : never 
  // 走到这,结果为 Fibonacci<3, [1, Calculate<A>]>
  : Fibonacci<3, [1, Calculate<[1]>]>;

1.1 根据上一步的结果,代入到 Calculate中,计算出数列第 2 个的值:

type Calculate<[1]> =
  [1] extends [...any, infer A, infer B]	// 条件不成立
  ? [...GenerateTuple<A extends number ? A : never>, ...GenerateTuple<B extends number ? B : never>]['length']
  // 走到这,结果为 1
  : T['length'];

// 最终结果为:
Fibonacci<3, [1, 1]>

2. 根据第一步的结果,再次代入到Fibonacci中:

type Fibonacci<3, [1, 1]> =
  2 extends 3		// 条件不成立 
  ? 
    A extends [...any, infer E] 
    ? E 
    : never 
  // 走到这,结果为 Fibonacci<3, [1, 1, Calculate<[1, 1]>]>
  : Fibonacci<3, [1, 1, Calculate<[1, 1]>]>

2.1 使用Calculate计算第数列中第 3 个值:

type Calculate<[1, 1]> =
  [1, 1] extends [...any, infer A, infer B]	// 条件成立
  ? 
    /*
      走到这里,GenerateTuple 就是用来生成一个指定长度的元组
      这样子把 A 和 B 加起来,就是下一个值
      所以最终结果:[1, 1]['length'] = 2
    */
    [
      ...GenerateTuple<1>,
      ...GenerateTuple<1>
    ]['length']
  : T['length'];

// 最终结果为:
Fibonacci<3, [1, 1, 2]>

3. 根据第 2 步的最终结果继续:

type Fibonacci<3, [1, 1, 2]> =
  3 extends 3		// 条件成立 
  ? 
    [1, 1, 2] extends [...any, infer E] 	// 条件成立 
    // 走到这,最终结果为 2
    ? 2
    : never 
  : Fibonacci<T, [...A, Calculate<A>]>;

最终结果为 2,符合 case ,测试通过。

我想出来的比较复杂,虽然做出来了,但不妨碍去看看更好更简洁的解答

type Fibonacci
<
  T extends number,
  // 记录数列的索引,就是计算到第几个值了
  CurrentIndex extends any[] = [1],
  // 数列倒数第二个值
  Prev extends any[] = [],
  // 数列最后面一个值
  Current extends any[] = [1]
> = 
  // 当数列计算的索引与 T 一致时,就可以返回数列最后一个值的长度了
  CurrentIndex['length'] extends T
  ? 
    Current['length']
  : 
    Fibonacci<T, [...CurrentIndex, 1], Current, [...Prev, ...Current]>