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

Wea*_*sel 5 typescript typescript-typings

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

这种类型可以采取多种形式。它可以是静态值、响应式(特定于断点的)值,也可以是由空格分隔的两者的组合。

可能的(奇异)值如下:

type CardSize =
'compact' |
'normal' |
'compact@small' |
'compact@medium' |
'compact@large' |
'normal@small' |
'normal@medium' |
'normal@large';
Run Code Online (Sandbox Code Playgroud)

我最终想要的类型如下:

type CardSize = 
'compact' | 
... |
'normal@large' |
'compact normal@medium' |
'compact compact@small normal@medium' | 
'compact@small normal@large' etc.
Run Code Online (Sandbox Code Playgroud)

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

type CardSize = Size | `${Size}@${Breakpoint}`;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

const size: CardSize = 'compact@small @normal@large';
const size: CardSize = 'normal@large compact@small';
Run Code Online (Sandbox Code Playgroud)

有人知道如何实现这种类型的排列吗?即使这意味着没有这两个限制,这也会有很大的帮助!

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

jca*_*alz 3

您绝对可以生成字符串值的排列/组合的联合类型,通过模板文字类型递归连接。当然,随着要排列和组合的元素数量的增加,排列和组合的数量会增长得相当快。TypeScript 只能处理数万个元素的构建联合,当接近这个数量时,编译器性能往往会受到影响。因此,这种方法仅适用于少量元素。

您的CardSize示例会很好,因为您只有两种大小和四个断点:

type CardSize = BuildCardSizes<'compact' | 'normal', '' | '@small' | '@medium' | '@large'>
Run Code Online (Sandbox Code Playgroud)

其中是一个适当定义的类型函数,它允许您根据需要BuildCardSizes<S, B>使用任何内容,但最多只允许使用一次元素。我是这样定义它的:SB

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;
Run Code Online (Sandbox Code Playgroud)

其作用是采用B断点的并集并使用分配条件类型将其拆分为其组成成员。这就是B extends any ? (...) : never括号内的部分,B只是该联合的一个元素。请注意,我们还需要完整的联合。TypeScript 并不容易做到这一点,所以我使用BB另一个类型参数 ,它默认为原始B. 在下文中,B表示“当前断点联合的某个特定元素”,而BB表示“当前完整断点联合”。

因此,对于每个B,可接受的卡片大小要么是某些元素与特定元素`${S}${B}`的串联;要么是某些元素与特定元素的串联。或者,这是相同的东西,后跟一个空格,然后...这是您使用相同的卡片尺寸得到的集合,但从完整元素列表中删除。SB`${S}${B} ${BuildCardSizes<S, Exclude<BB, B>>}`BuildCardSizes<S, Exclude<BB, B>>SBBB

让我们在您的示例上测试一下:

/* 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" | ... */
Run Code Online (Sandbox Code Playgroud)

呃,哇,编译器对……检查注释…… 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
Run Code Online (Sandbox Code Playgroud)

看起来不错!

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

Playground 代码链接