寻找由模板文字类型的任何可能组合组成的 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 某些给定值是可接受的。不过,它更复杂,超出了这个问题的范围。
对于我的项目,我需要想出一个 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 某些给定值是可接受的。不过,它更复杂,超出了这个问题的范围。