当你有一个递归条件类型 like StateUnion
并将其应用于本身递归的类型,例如Config
,您很容易陷入编译器抱怨递归深度的情况。有时您可以调整一个或两个定义来回避问题,但这些方法往往是反复试验(至少根据我的经验)。
您可能尝试的一种方法是为递归条件类型提供明确的深度限制机制,选择合理的默认最大深度但允许用户更改它。为您StateUnion
它可能看起来像这样:
type StateUnion<T, D extends number = 8, A extends any[] = []> =
A['length'] extends D ? never : (
T extends { states: Record<infer K, any> }
? K | StateUnion<T['states'][K], D, [0, ...A]>
: never);
这里的预期用途是StateUnion<T, D>
where D
是一个数字文字类型对应于所需的最大深度。如果你写StateUnion<T>
没有指定D
那么最大深度是8
(但你可以改变它)。
不幸的是 TypeScript(从 v5.1 开始)不知道如何直接在类型级别进行数学运算,所以我不能轻易地说StateUnion<T['states'][K], D-1>
。相反,我求助于使用元组类型具有已知的数字文字length
财产,以及可变元组类型来操纵这些类型。因此,我不必说“如果深度为 0,则退出,否则将深度减少 1 并递归”,而不是正常的“如果该数组的长度与最大深度相同,则退出,否则附加一个元素到数组并递归”。所以我有累加器数组A
在那里,我们正在检查A["length"]
并传入[0, ...A]
作为新的A
.
我们来测试一下:
interface Test {
states: { a: { states: { b: { states: { c: {
states: { d: { states: { e: Test } } }
} } } } } }
}
type SU2 = StateUnion<Test, 2>
//type SU2 = "a" | "b"
type SU4 = StateUnion<Test, 4>
//type SU4 = "a" | "b" | "c" | "d"
type SU8 = StateUnion<Test> // defaults to 8
// type SU8 = "a" | "b" | "c" | "d" | "e"
这按预期工作,如果我进行更改,那么您的原始代码StateUnion<Config>
(or StateUnion<T>
where T extends Config
) 编译没有错误。
Playground 代码链接