你可以使用递归条件类型来实现这一点:
type Last<T extends unknown[]> =
T extends [...unknown[], infer U]
? U
: never
type JoinedUnion<
Format extends string,
Limit extends number,
Acc extends string[] = [Format],
> =
Last<Acc> extends string
? Acc['length'] extends Limit
? Acc[number]
: JoinedUnion<
Format,
Limit,
[...Acc, `${Last<Acc>} ${Format}`]
>
: never
首先使用Last
来获取元组的最后一个索引的类型,这将在后面用到.
那么这款JoinedUnion
型就可以:
Format
作 for each 术语的字符串文字
Limit
允许的最大递归级别.这会告诉Typescript 什么时候该退出.
Acc
是到目前为止累积的字符串文字类型的元组,用于在内部收集结果并知道何时停止.
逐行:
Last<Acc> extends string
解决了一个类型错误,我不完全确定为什么它是一个错误.但这向类型判断器证明了元组中的最后一项是字符串类型.
? Acc['length'] extends Limit
这会判断我们是否达到了极限.
? Acc[number]
已达到限制,因此将构建元组索引为number
,以获得所有值的联合.
然后我们开始递归:
: JoinedUnion<
Format,
Limit,
[...Acc, `${Last<Acc>} ${Format}`]
>
如果我们还没有达到预期的长度,我们就会到达这里.因此,我们使用相同的格式和限制再次调用JoinedUnion
类型,但将一个条目附加到我们正在构建的元组中.该元组是我们之前构建的所有内容(...Acc
),然后是一个字符串文字类型的新项,其中元组中的最后一项加上一个空格和所需的格式.这一部分是构建下一层的基础.
让我们来测试一下:
type TestAB = JoinedUnion<'a' | 'b', 3>
"a" | "b" | "a a" | "a b" | "b a" | "b b" | "a a a" | "a a b" | "a b a" | "a b b" | "b a a" | "b a b" | "b b a" | "b b b"
我认为这就是你想要的.
对于您的示例值,它也同样适用:
export type Format = `${number}` | `${number}px`;
export type Input = JoinedUnion<Format, 12>
const inputA: Input = '1';
const inputB: Input = '1 2';
const inputC: Input = '1 2 3';
const inputD: Input = '1 2 10px';
const inputE: Input = '10px 1 2 20px';
const inputBad: Input = '10px 1 2 20pxz'; // error
See playground个
注意:这将解析为拥有超过8000个成员的联盟类型.如果您采用长度16,这使您的成员长度约为131,000,那么在抛出以下代码之前,这似乎是最大打字脚本所允许的:
Expression produces a union type that is too complex to represent.(2590)
这可能会使类型判断变慢.
这些权衡是否适合您的用例取决于您.