递归通用条件类型定义导致错误(TypeScript)
Recursive generic conditional type definition causes error (TypeScript)
考虑以下几点:
export type UwrapNested<T> = T extends Iterable<infer X> ? UwrapNested<X> : T
我收到类型错误(使用 TypeScript 3.9.4):
类型 'UwrapNested' 不是通用的
这是因为类型系统的某些限制吗?
是的,条件类型不能以这种方式直接递归。参见 microsoft/TypeScript#26980。人们使用许多技巧来规避这一点。我唯一推荐的方法是将你的类型循环展开到一个几乎相同的类型列表中,这些类型在某个固定的深度退出,就像这样:
type UnwrapNested<T> = T extends Iterable<infer X> ? UN0<X> : T;
type UN0<T> = T extends Iterable<infer X> ? UN1<X> : T;
type UN1<T> = T extends Iterable<infer X> ? UN2<X> : T;
type UN2<T> = T extends Iterable<infer X> ? UN3<X> : T;
type UN3<T> = T extends Iterable<infer X> ? UN4<X> : T;
// ...
type UN4<T> = T extends Iterable<infer X> ? UNX<X> : T;
type UNX<T> = T; // bail out
您可以根据编译器性能和预期用例来选择“纾困”深度。是的,它很丑,但它很直接而且“合法”:
type UOkay = UnwrapNested<string[][][][]> // string
type UTooMuch = UnwrapNested<string[][][][][][][][]> // string[][]
提到了其他技巧,它们使用 distributive conditional types 来延迟求值并欺骗编译器认为类型不是递归的,但这些技巧很脆弱并且有非常奇怪的副作用并且不一定得到支持。例如
type UnwrapNestedIllegal<T> = {
base: T, recurse: UnwrapNestedIllegal<T extends Iterable<infer X> ? X : never>
}[T extends Iterable<any> ? "recurse" : "base"]
在您尝试使用它之前,这看起来似乎是可能的,然后您会收到循环错误:
type Oops = UnwrapNestedIllegal<string> // any
有什么方法可以改变 UnwrapNestedIllegal
来解决这个问题吗?大概。这是个好主意吗?我说“不”,尽管我可能 extreme views on the subject。事实上,我非常希望有一种官方支持的方式来获取递归条件类型,但是如果没有语言设计者的坚定承诺,我不能凭良心推荐任何人使用这些技巧。
好的,希望对您有所帮助;祝你好运!
这就是我最终解决问题的方式,@jcalz 证实了这一点:
type UnwrapIterable1<T> = T extends Iterable<infer X> ? X : T
type UnwrapIterable2<T> = T extends Iterable<infer X> ? UnwrapIterable1<X> : T
type UnwrapIterable3<T> = T extends Iterable<infer X> ? UnwrapIterable2<X> : T
export type UnwrapNestedIterable<T> = T extends Iterable<infer X> ? UnwrapIterable3<X> : T
考虑以下几点:
export type UwrapNested<T> = T extends Iterable<infer X> ? UwrapNested<X> : T
我收到类型错误(使用 TypeScript 3.9.4): 类型 'UwrapNested' 不是通用的
这是因为类型系统的某些限制吗?
是的,条件类型不能以这种方式直接递归。参见 microsoft/TypeScript#26980。人们使用许多技巧来规避这一点。我唯一推荐的方法是将你的类型循环展开到一个几乎相同的类型列表中,这些类型在某个固定的深度退出,就像这样:
type UnwrapNested<T> = T extends Iterable<infer X> ? UN0<X> : T;
type UN0<T> = T extends Iterable<infer X> ? UN1<X> : T;
type UN1<T> = T extends Iterable<infer X> ? UN2<X> : T;
type UN2<T> = T extends Iterable<infer X> ? UN3<X> : T;
type UN3<T> = T extends Iterable<infer X> ? UN4<X> : T;
// ...
type UN4<T> = T extends Iterable<infer X> ? UNX<X> : T;
type UNX<T> = T; // bail out
您可以根据编译器性能和预期用例来选择“纾困”深度。是的,它很丑,但它很直接而且“合法”:
type UOkay = UnwrapNested<string[][][][]> // string
type UTooMuch = UnwrapNested<string[][][][][][][][]> // string[][]
提到了其他技巧,它们使用 distributive conditional types 来延迟求值并欺骗编译器认为类型不是递归的,但这些技巧很脆弱并且有非常奇怪的副作用并且不一定得到支持。例如
type UnwrapNestedIllegal<T> = {
base: T, recurse: UnwrapNestedIllegal<T extends Iterable<infer X> ? X : never>
}[T extends Iterable<any> ? "recurse" : "base"]
在您尝试使用它之前,这看起来似乎是可能的,然后您会收到循环错误:
type Oops = UnwrapNestedIllegal<string> // any
有什么方法可以改变 UnwrapNestedIllegal
来解决这个问题吗?大概。这是个好主意吗?我说“不”,尽管我可能 extreme views on the subject。事实上,我非常希望有一种官方支持的方式来获取递归条件类型,但是如果没有语言设计者的坚定承诺,我不能凭良心推荐任何人使用这些技巧。
好的,希望对您有所帮助;祝你好运!
这就是我最终解决问题的方式,@jcalz 证实了这一点:
type UnwrapIterable1<T> = T extends Iterable<infer X> ? X : T
type UnwrapIterable2<T> = T extends Iterable<infer X> ? UnwrapIterable1<X> : T
type UnwrapIterable3<T> = T extends Iterable<infer X> ? UnwrapIterable2<X> : T
export type UnwrapNestedIterable<T> = T extends Iterable<infer X> ? UnwrapIterable3<X> : T