首页 > 解决方案 > 如何强制 Set 对象包含枚举中的所有值 - TS

问题描述

我有以下枚举:
enum GREETINGS { HELLO = 'Hello', HI = 'Hi' }

我想为 Set 数据结构创建一个类型,该类型将强制用户在其中包含所有枚举值,如下所示:
type MyGreetings = Set<GREETINGS>

预期结果应该会突出显示以下行,并发出警告,指出并非所有必需的值都包含在结果中:
const result: MyGreetings = new Set([GREETINGS.HELLO]);

标签: typescriptenumsset

解决方案


一个问题是 TypeScript 认为Set<GREETINGS.HELLO>可以分配给 / 兼容 Set<GREETINGS>; 从理论上讲,这应该意味着如果你想Set<GREETINGS>被分配给MyGreetings,那么通过可分配性的传递性, Set<GREETINGS.HELLO>也将被分配给MyGreetings,并且你在开始之前就停止了。但:

Set<GREETINGS.HELLO>可分配的事实实际上是Set<GREETINGS>在类型系统中使用了一个故意的漏洞:方法参数 bivariance。方法参数双变量非常有用,但它不是类型安全的:

const justHello = new Set<GREETINGS.HELLO>([GREETINGS.HELLO]);
const stillJustHello: Set<GREETINGS> = justHello;
stillJustHello.add(GREETINGS.HI) // <-- no error, oops

通过“扩大”justHellostillJustHello,编译器忘记了它不应该接受除 之外的任何成员GREETINGS.HELLO

因此,更接近您想要的一种方法是使用编译--strictFunctionType器选项(它是编译器选项套件的一部分--strictSet,通常建议使用)并使用函数属性类型而不是方法签名重写至少一个方法签名。让我们看一下add(),看看有什么区别:

type Method = { add(x: GREETINGS): void } // method syntax
const m: Method = new Set<GREETINGS.HELLO>(); // okay

type FuncProp = { add: (x: GREETINGS) => void } // function syntax
const f: FuncProp = new Set<GREETINGS.HELLO>(); // error

所以让我们尝试一下:

type MyGreetings = Set<GREETINGS> & { add: (g: GREETINGS) => MyGreetings };

如果我们使用它,您将获得所需的结果:

const result: MyGreetings = new Set([GREETINGS.HELLO]); // error!
//  Type 'GREETINGS' is not assignable to type 'GREETINGS.HELLO'

const okay: MyGreetings = new Set([GREETINGS.HELLO, GREETINGS.HI]); // okay

万岁!


嗯,有点。编译器确实无法确切知道哪些值已传递到Set. 它可以推断出某些类型new Set(...),但有一些方法可以手动指定更宽的值或让编译器推断出更宽的值。然后上面的所有进展都丢失了。Set<X>XXX

例如,由于Set<GREETINGS>允许 a 仅包含 的子集GREETINGS,因此可以执行以下手动规范:

const oops: MyGreetings = new Set<GREETINGS>([GREETINGS.HELLO]) // no error

或者有人可以让编译器以这种方式推断联合:

const alsoOops: MyGreetings = new Set([
  Math.random() < 0.5 ? GREETINGS.HELLO : GREETINGS.HI
]); // no error

这些都不是错误。等号右侧的值为 a Set<GREETINGS>。而且您需要Set<GREETINGS>可分配给MyGreetings,因此分配也不是错误。对此无能为力。

如果在实践中您不认为有人会以这些或其他病态方式创建集合,那么也许您可以使用MyGreetings. 否则,如果你真的需要保证某些东西,你应该考虑以某种方式改变你的设计。但这可能超出了问题的范围,所以我会停在那里。


Playground 代码链接


推荐阅读