【问题标题】:Looking for a TypeScript type that consists of any possible combination of Template Literals Types寻找由模板文字类型的任何可能组合组成的 TypeScript 类型
【发布时间】:2021-11-07 08:29:41
【问题描述】:

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

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

可能的(单数)值如下:

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}`;

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

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

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

其次,如果排列的顺序无关紧要,那就太好了。我会考虑以下相同:

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

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

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

【问题讨论】:

  • 我很难准确理解您的限制,"compact normal" 是否允许?还是认为两个相同的断点(即“空”断点)?无论如何this 对你有用吗?工会有大约 630 名成员,所以我无法轻松检查所有成员。如果我遗漏了一个用例,请告诉我它是什么。
  • 感谢您向我指出该解决方案 @captain-yossarian。它非常接近我正在寻找的内容,只是在我的情况下会有太多冗余变体,这意味着类型可能相同,但顺序不同。 jcalz 提供的解决方案也解决了这些重要的限制。
  • 好的,有机会我会写答案的。
  • 如果类型太复杂也无法编译,那么任何单一类型都不会起作用,并且在不限制可能性的情况下检查每个单词会使它更糟(因为像"compact normal" 这样的东西现在会被接受,增加工会的规模)。相反,您可以使用辅助函数执行通用检查器之类的操作。它更复杂。喜欢this。但这超出了这个问题的范围;如果您想为此提出一个新问题,我也许也可以回答(当我回答时!????)

标签: typescript typescript-typings


【解决方案1】:

您绝对可以生成一个由字符串值的排列/组合组成的union type,通过template literal types 递归连接。当然,当您增加要排列和组合的元素数量时,排列和组合的数量会迅速增长。 TypeScript 只能处理数万个元素的联合,当你接近这个时,编译器的性能往往会受到影响。所以这种方法只适用于少量元素。

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

type CardSize = BuildCardSizes<'compact' | 'normal', '' | '@small' | '@medium' | '@large'>

其中BuildCardSizes&lt;S, B&gt; 是一个适当定义的类型函数,它允许您尽可能多地使用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&lt;S, Exclude&lt;BB, B&gt;&gt;}`,这是相同的东西,后跟一个空格,然后是BuildCardSizes&lt;S, Exclude&lt;BB, B&gt;&gt;... 这是您使用相同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

【讨论】:

  • 非常感谢您提供详尽的答案、示例以及所有这些背后的推理!编译器性能并不是我在编写问题时真正考虑的事情,我也从未遇到过诸如分布式条件类型之类的东西。非常非常有见地的东西,它肯定会引导我朝着正确的方向前进。还感谢您指出使用通用约束方法对更复杂类型进行类型检查的替代方法。非常非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-18
  • 2021-04-11
  • 1970-01-01
  • 2020-03-17
  • 2021-09-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多