在 TypeScript 中组合枚举

Combining enums in TypeScript

在 TypeScript 中组合多个枚举的好方法是什么?我的第一直觉会告诉我按如下方式做,但这会产生容易出错的代码重复。

export enum Formats {
  Shirt = 'shirt',
  Fruit = 'fruit',
}

export enum Shirt {
  Yellow = 'yellow',
  Orange = 'orange',
}

export enum Fruit {
  Orange = 'orange',
  Lemon = 'lemon',
}

export enum Item {
    ShirtYellow = 'shirt:yellow',
    ShirtOrange = 'shirt:orange',
    FruitOrange = 'fruit:orange',
    FruitLemon = 'fruit:lemon',
}

用例示例。枚举用于描述四种不同的对话 windows。衬衫对话框处理程序定义了两个对话框 windows 黄色和橙色。黄色衬衫对话框和橙色衬衫对话框差异如此之大,以至于无法对它们使用相同类型的对话框。衬衫对话处理程序不理解水果对话。水果处理机类似但相反。还有一个全局对话框管理器负责确保在任何给定时间只有一个对话框打开。全局 window 管理器包含一个代表打开对话框的变量。此变量存储在磁盘上以在 app/page 重新加载后保留打开的对话框状态。

玩了一会儿之后,我想到了以下内容。仍然存在重复,但至少有一些交叉检查可能有助于防止错误。这样做的主要问题是它非常冗长,而且很容易忘记一种组合。

type Pair<A, B> = [A, B]
const pair = <A, B>(a: A, b: B): Pair<A, B> => [a, b]

type ShirtYellow = Pair<Formats.Shirt, Shirt.Yellow>
type ShirtOrange = Pair<Formats.Shirt, Shirt.Orange>
type FruitOrange = Pair<Formats.Fruit, Fruit.Orange>
type FruitLemon = Pair<Formats.Fruit, Fruit.Lemon>

const ShirtYellow: ShirtYellow = pair(Formats.Shirt, Shirt.Yellow)
const ShirtOrange: ShirtOrange = pair(Formats.Shirt, Shirt.Orange)
const FruitOrange: FruitOrange = pair(Formats.Fruit, Fruit.Orange)
const FruitLemon: FruitLemon = pair(Formats.Fruit, Fruit.Lemon)

export type Item = ShirtYellow | ShirtOrange | FruitOrange | FruitLemon 
export const Item = { ShirtYellow, ShirtOrange, FruitOrange, FruitLemon };

这是我的第二次尝试。这次是基于对象的解决方案。

type AbstractItem<I extends { kind: Formats, type: any }> = I

export type ShirtItem = AbstractItem<{kind: Formats.Shirt, type: Shirt}>
export type FruitItem = AbstractItem<{kind: Formats.Fruit, type: Fruit}>

export type Item = AbstractItem<ShirtItem | FruitItem>

export const isShirt = (i: Item): i is ShirtItem => i.kind === Formats.Shirt
export const isFruit = (i: Item): i is FruitItem => i.kind === Formats.Fruit

export const getShirt = (i: Item): Shirt|null => isShirt(i) ? i.type : null
export const getFruit = (i: Item): Fruit|null => isFruit(i) ? i.type : null

我认为我们不应该关注像枚举这样的原始值类型。适当的记录或 class 可以做你想做的事。 TypeScript 允许您构建 "discriminated unions",即可以通过一个字段("tag")区分的一系列类型:

export enum ShirtOptions {
  Yellow = 'yellow',
  Orange = 'orange',
}

export enum FruitOptions {
  Orange = 'orange',
  Lemon = 'lemon',
}

interface Shirt {
    kind: 'shirt';
    options: ShirtOptions;
}

interface Fruit {
    kind: 'fruit';
    options: FruitOptions; // Can have a different name
}

type Format = Shirt | Fruit;

function handler(f: Format) {
    switch (f.kind) {
        case "shirt": return doShirtStuff();
        case "fruit": return doFruitStuff();
    }
}

并且 TypeScript 会对 switch 语句进行详尽检查,如果您没有处理所有情况,它会告诉您(有关详细信息,请参阅 link)。