扩展递归泛型类型时类型推断失败

Broken type inference when extending recursive generic type

我处于以下情况:

interface Rec {
  key: string;
  children: this[];
}
type Recursive<D extends string> = {
  [K in D]: string;
} & Rec;

type FlattenRecursive = 
  <D extends string, R extends Recursive<D>>(rs: R[]) => Omit<R, "children">[] 

const flatten: FlattenRecursive = 
  rs => rs.flatMap(r => flatten(r.children))

Playground

我希望对 flatten 函数的递归调用被推断为 flatten<D, R> 而不是当前的 <string, R>。因此,我不得不在调用它时显式注释类型参数:

type RValueAnimal = Recursive<"value"> & { animal: string }

const rvalues: RValueAnimal[] = [
  // ...
]

flatten(rvalues) // <- error
flatten<"value", RValueAnimal>(rvalues)

Playground

我该如何解决这个问题?

[generic]https://www.typescriptlang.org/docs/handbook/2/generics.html) 函数类型的问题

<D extends string, R extends Recursive<D>>(rs: R[]) => Omit<R, "children">[] 

是类型参数D没有推理站点。您可能希望编译器能够使用 generic constraint of Recursive<D> for the type parameter R. Unfortunately, generic constraints do not serve as inference sites for other type parameters. There was a suggestion at microsoft/TypeScript#7234 推断出 D 来执行此操作,但它从未实现过。

这意味着当您调用这样的函数时,D 的推理将失败。因此它将回落到它自己的 string 约束,然后 R 将被约束到 Recursive<string>,这会破坏事情。


我的建议是完全删除 D,并纯粹用 R 来表达您关心的约束。例如:

type FlattenRecursive =
  <R extends Recursive<Extract<Exclude<keyof R, keyof Rec>, string>>>(
    rs: R[]) => Omit<R, "children">[]

这有点令人费解,但想法是你所说的 D 可以从 keyof R 计算出来(它应该只是 [=16] 的那些 string 键=] 也不是 Rec 的键)。您可以根据需要使用它:

flatten(rvalues); // okay

Playground link to code