如何强制 Set 对象包含枚举中的所有值 - TS

How to force a Set object to contain all values within an enum - TS

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

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

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

一个问题是 TypeScript 认为 Set<GREETINGS.HELLO>assignable to / compatible with Set<GREETINGS>; in theory that should mean that if you want Set<GREETINGS> to be assignable to MyGreetings, then by transitivity 的可分配性,Set<GREETINGS.HELLO> 也将可分配给 MyGreetings,你在你之前就停止了'已经开始了。但是:

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

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

通过“扩大”justHellostillJustHello,编译器忘记了它不应该接受除 GREETINGS.HELLO.

之外的任何成员

因此,一种更接近您想要的方法是使用 the --strictFunctionType compiler option (which is part of the --strict suite of compiler options and is recommended in general) and rewrite at least one Set method signature using a function property type 而不是方法签名。让我们看看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(...) 对于某些 XSet<X> 类型,但是有一些方法可以手动指定更宽的 X 或让编译器推断出更宽的 X。然后上面的所有进度都丢失了。

例如,由于允许 Set<GREETINGS> 仅包含 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

这些都不是错误;等号右边的值是 Set<GREETINGS>。而且你需要 Set<GREETINGS> 可以分配给 MyGreetings,所以分配也不是错误。对此无能为力。

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


Playground link to code