typescript 创建联合类型以确保处理“enum”的每个值

mhd8tkvw  于 2023-01-21  发布在  TypeScript
关注(0)|答案(1)|浏览(140)

我有一个操作枚举(不幸的是,我不能改变它):

enum OpType {
  OpA = 0,
  OpB = 1,
}

...以及一组用于携带每个操作所需数据的对象的类型:

type A = {
  readonly opType: OpType.OpA;
  readonly foo: number;
};

type B = {
  readonly opType: OpType.OpB;
  readonly bar: string;
};

......最后是确保每个操作都得到处理的处理程序函数:

type Ops = A | B;

export const ensureExhaustive = (_param: never) => {};

export const handleOp = (op: Ops) => {
  switch (op.opType) {
    case OpType.OpA:
      if (op.foo < 80) { /* … */ }
      break;
    case OpType.OpB:
      if (op.bar === 'foo') { /* … */ }
      break;
    default:
      ensureExhaustive(op);
  }
}

然而,这个handleOp函数只能确保我们处理显式添加到Ops联合体的内容--与OpType枚举的连接已经丢失,因此如果OpC = 2被添加到enum,将不会检测到这没有被处理。
如何将枚举值(可能通过Ops类型)“连接”到handleOp中的switch语句,以确保处理每个值?

3bygqnnd

3bygqnnd1#

您可以将ensureExhaustive函数重新创建为类型:

type EnsureExhaustive<T extends never> = T;

然后你可以定义一个伪类型来确保某个类型是never

type OpsMatchesEnum = EnsureExhaustive<Exclude<OpType, Ops["opType"]>>;

方便的是,存在一个实用程序Exclude,它已经具有我们想要的行为:

Exclude<1 | 2 | 3, 1 | 2>;     // 3
Exclude<1 | 2 | 3, 1 | 2 | 3>; // never

换句话说,如果第二个参数包含第一个参数,则此类型的结果为never。转换到我们的用例,如果Ops["opType"]使用OpType的所有成员,则此类型的结果为never
我们可以做的另一个技巧是使ensureExhaustive成为 generic,这样它就具有签名

export const ensureExhaustive = <T extends never>(_param: T) => {};

因此我们可以使用Python 4.7中引入的示例化表达式,而无需使用EnsureExhaustive类型:

type OpsMatchesEnum = typeof ensureExhaustive<Exclude<OpType, Ops["opType"]>>;

当然,如果您启用了noUnusedLocals或使用了linter,则此伪类型可能会导致错误或警告。

相关问题