如何使用模板文字类型项生成数组

How to generate array with Template Literal Types items

我有一个 UNO 卡片组,我希望它是类型安全的。 deck 数组中的每张卡片都由一个包含颜色和值的 2 字符字符串表示。我正在为 CardType 使用 Template Literal Type

type SpecialCard = "S" | "R" | "D"; // Skip | Reverse | Draw 2
type NumberCard = number;
type Color = "R" | "Y" | "G" | "B";
type WildCard = "W" | "W4";
type CardType = `${Color}${(NumberCard | SpecialCard)}` | WildCard;

现在我想创建一组这样的卡片:

const StandardDeck: CardType[] = (["R", "Y", "G", "B"] as Color[])
  .map<CardType[]>((color) =>
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "S", "R", "D"].map<CardType>(
      (v) => `${color}${v}`
    )
  ).flat()

但是这在这一行给我一个错误:

(v) => `${color}${v}`
Type '`R${number}` | `Y${number}` | `G${number}` | `B${number}` | `R${string}` | `Y${string}` | `G${string}` | `B${string}`' is not assignable to type 'CardType'.
  Type '`R${string}`' is not assignable to type 'CardType'.
    Type 'string' is not assignable to type 'CardType'.
      Type '`R${string}`' is not assignable to type '"BD"'.
        Type 'string' is not assignable to type '"BD"'.

Playground Link

type SpecialCard = "S" | "R" | "D"; // Skip | Reverse | Draw 2
type NumberCard = number;
type Color = "R" | "Y" | "G" | "B";
type WildCard = "W" | "W4";

type Indexes = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

type CardType = `${Color}${(NumberCard | SpecialCard)}` | WildCard;


const applyColor = <Colors extends Color[]>(colors: [...Colors]) =>
  <ColorKeys extends Indexes | SpecialCard, ColorMap extends ColorKeys[]>(colorMap: [...ColorMap]) =>
    colors
      .map((color) =>
        colorMap.map((v): `${Colors[number]}${ColorKeys}` => `${color}${v}`)
      ).flat()

const makeDeck = applyColor(["R", "Y", "G", "B"])

// ("R0" | "R4" | "R1" | "R2" | "R3" | "R5" | "R6" | "R7" | "R8" | "R9" | "RR" | "RS" 
// | "RD" | "Y0" | "Y4" | "Y1" | "Y2" | "Y3" | "Y5" | "Y6" | "Y7" | "Y8" | "Y9" | "YR" | "YS" | "YD" | "G0" | ... 24 more ... | "BD")[]
// 28+24 = 52 elements in the union
// exactly same number of elements is in runtime value
const StandardDeck = makeDeck([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "S", "R", "D"])

Playground

说明

我使用 curry 模式使其更具可读性,并使用 variadic tuple types 进行推理

您可能已经注意到,为了推断类型,我使用了一个函数,而不仅仅是一个值。

如果你想输入复杂的东西,首先尝试推断每个函数参数。推理越严格越好。

将鼠标悬停在 applyColor(["R", "Y", "G", "B"]) 上。你会看到数组是用每个元素推断出来的,而不仅仅是 string[]。与 makeDeck.

相同

推断基元元组很容易。您只需要为元组 Colors 创建泛型并应用适当的约束 extends Color[].

在第二个函数中,除了推断整个元组外,我还推断了一个元组元素ColorKeys。我这样做是因为我在 (v) => ${color}${v}`` 中使用它来输入 return 类型。

TypeScript 足够聪明,可以推断 ${color}${v}.

的确切类型

如果您对函数参数推断感兴趣,可以阅读我的 article。它还没有完成,但已经有一些有用的解释和示例。

P.S。似乎 WildCard 在运行时不存在。