寻找由模板文字类型的任何可能组合组成的 TypeScript 类型

Looking for a TypeScript type that consists of any possible combination of Template Literals Types

对于我的项目,我需要想出一个 TypeScript 类型,也就是所谓的 CardSize。

这种类型可以有多种形式。它可以是静态值、响应式(特定于断点的)值,也可以是两者的组合,由白色 space.

分隔

可能的(奇异)值如下:

type CardSize =
'compact' |
'normal' |
'compact@small' |
'compact@medium' |
'compact@large' |
'normal@small' |
'normal@medium' |
'normal@large';

我最终想要的类型是这样的:

type CardSize = 
'compact' | 
... |
'normal@large' |
'compact normal@medium' |
'compact compact@small normal@medium' | 
'compact@small normal@large' etc.

第一步似乎是使用模板文字类型,所以我涵盖了所有单数类型:

type CardSize = Size | `${Size}@${Breakpoint}`;

接下来,我尝试研究排列以获得可能值的任意组合,但到目前为止运气不好。

如果我能以某种方式设置这两个约束,那就太好了:

将可能的组合数量限制为只能同时分配一个特定的断点值(例如,不要在同一字符串中同时具有 'compact@small''normal@small

其次,如果排列顺序无关紧要就好了。我会认为以下相同:

const size: CardSize = 'compact@small @normal@large';
const size: CardSize = 'normal@large compact@small';

有人知道如何实现这种排列吗?即使这意味着没有两个约束,这将是一个很大的帮助!

回复:我意识到排列类型对于我想要实现的目标来说有点矫枉过正。我可以在不依赖 | string 作为后备的情况下为 CardSize 强制类型安全吗?

你绝对可以生成一个union type of permutations/combinations of string values, recursively concatenated via 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 to split it into its constituent members. Thats the B extends any ? (...) : never part, and inside the parentheses, B is just a single element of that union. Note that we also need the full union. TypeScript doesn't make it easy to do that, so I'm using BB, another type parameter, which defaults 到原始 B。在下文中,B 表示“当前断点并集的某个特定元素”,而 BB 表示“完整的当前断点并集”。

因此,对于每个 B,可接受的卡片大小是 `${S}${B}`S 的某些元素与特定元素 B 的串联;或 `${S}${B} ${BuildCardSizes<S, Exclude<BB, B>>}`,这是相同的东西,后面跟着 space,然后是 BuildCardSizes<S, Exclude<BB, B>>...这是你用相同的 S 得到的一组卡片尺寸,但是B 从完整元素列表中删除 BB

让我们用你的例子来测试它:

/* 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

看起来不错!

正如我在评论中提到的,对于更多的元素还有其他方法;不是生成所有可能的可接受值的特定并集,而是使用通用约束 check 某些给定值是可接受的。不过,它更复杂,超出了这个问题的范围。

Playground link to code