您绝对可以生成一个由字符串值的排列/组合组成的union type,通过template literal types 递归连接。当然,当您增加要排列和组合的元素数量时,排列和组合的数量会迅速增长。 TypeScript 只能处理数万个元素的联合,当你接近这个时,编译器的性能往往会受到影响。所以这种方法只适用于少量元素。
您的 CardSize 示例会很好,因为您只有两个大小和四个断点:
type CardSize = BuildCardSizes<'compact' | 'normal', '' | '@small' | '@medium' | '@large'>
其中BuildCardSizes<S, B> 是一个适当定义的类型函数,它允许您尽可能多地使用S 中的任何内容,但最多只能使用一次B 的元素。这是我的定义:
type BuildCardSizes<S extends string, B extends string, BB extends string = B> =
B extends any ? (`${S}${B}` | `${S}${B} ${BuildCardSizes<S, Exclude<BB, B>>}`) : never;
它的作用是将断点合并B 并使用distributive conditional type 将其拆分为其组成成员。那是B extends any ? (...) : never 部分,在括号内,B 只是该联合的一个元素。请注意,我们还需要完整的联合。 TypeScript 并不容易做到这一点,所以我使用BB,另一个类型参数,defaults 到原来的B。在下文中,B 表示“当前断点联合的某些特定元素”,而BB 表示“当前完整的断点联合”。
因此,对于每个B,可接受的卡片大小是`${S}${B}`,S 的某些元素与特定元素B 的连接;或`${S}${B} ${BuildCardSizes<S, Exclude<BB, B>>}`,这是相同的东西,后跟一个空格,然后是BuildCardSizes<S, Exclude<BB, B>>... 这是您使用相同S,但从完整元素列表中删除B 获得的一组卡片大小@ 987654349@.
让我们在你的例子上测试一下:
/* type CardSize = "compact" | "normal" | "compact@small" | "normal@small" | "compact@medium" | "normal@medium" |
"compact@large" | "normal@large" | "compact@medium compact@large" | "compact@medium normal@large" |
"normal@medium compact@large" | "normal@medium normal@large" | "compact@large compact@medium" |
"compact@large normal@medium" | "normal@large compact@medium" | "normal@large normal@medium" |
"compact@small compact@medium" | "compact@small normal@medium" | "compact@small compact@large" |
"compact@small normal@large" | "compact@small compact@medium compact@large" |
"compact@small compact@medium normal@large" | "compact@small normal@medium compact@large" |
"compact@small normal@medium normal@large" | "compact@small compact@large compact@medium" |
"compact@small compact@large normal@medium" | "compact@small normal@large compact@medium" |
"compact@small normal@large normal@medium" | "normal@small compact@medium" | "normal@small normal@medium" |
"normal@small compact@large" | "normal@small normal@large" | "normal@small compact@medium compact@large" |
"normal@small compact@medium normal@large" | "normal@small normal@medium compact@large" |
"normal@small normal@medium normal@large" | "normal@small compact@large compact@medium" |
"normal@small compact@large normal@medium" | "normal@small normal@large compact@medium" |
"normal@small normal@large normal@medium" | "compact@large compact@small" | "compact@large normal@small" |
"normal@large compact@small" | "normal@large normal@small" | "compact@medium compact@small" |
"compact@medium normal@small" | "compact@medium compact@small compact@large" | ... */
呃,哇,编译器对...checks notes... 632 个元素的联合没有任何问题,但它太大了,我无法在这个答案中写出来或完全检查。不过无论如何,您可以从上面看到,大小被重用,但断点没有。
让我们抽查一下:
c = 'normal compact@small' // okay
c = 'compact@small normal' // okay
c = 'compact@small normal normal@large compact@medium' // okay
c = 'normal@small normal@medium normal@large normal' // okay
c = 'compact@small normal@small' // error
c = 'compact normal' // error
c = 'normal@small normal@medium normal@large normal normal@big' // error
c = '' // error
看起来不错!
正如我在 cmets 中提到的,还有其他方法可以处理大量元素;不是生成所有可能的可接受值的特定联合,而是对check 使用通用约束,即某个给定值是可接受的。不过,它更复杂,超出了这个问题的范围。
Playground link to code