TypeScript:为什么不强制使用确切的枚举类型?

时间:2019-08-02 23:15:05

标签: typescript

请查看以下简单代码:

const enum MyEnum {
    Zero
} 

const foo: MyEnum.Zero = 0 // OK as expected (since MyEnum.Zero is zero)
const bar: MyEnum.Zero = 1 // OK, but expected Error! Why?

在这种情况下,如何强制使用精确的窄数类型,即0

Playground

UPD:枚举似乎被破坏了https://github.com/microsoft/TypeScript/issues/11559

2 个答案:

答案 0 :(得分:4)

the enum section of the current TypeScript handbook中并没有真正提及它,但是所有number值都可以分配给任何数字枚举类型。新的TypeScript手册的当前草稿显示the following

  

由于历史上的限制,一些[assignability]规则很奇怪。例如,任何数字都可分配给数字枚举,但是对于字符串枚举则不是这样。只能将已知为字符串枚举一部分的字符串分配给它。那是因为数字枚举在联合类型和文字类型之前存在,所以它们的规则本来就宽松一些。

过去,似乎使用TypeScript中的数字枚举来支持bit fields,使用位掩码和按位运算来组合显式声明的枚举值以获得新值:

enum Color {
  Red = 0xFF0000,
  Green = 0x00FF00,
  Blue = 0x0000FF
}
const yellow: Color = Color.Red | Color.Green; // 0xFFFF00
const white: Color = Color.Red | Color.Green | Color.Blue; // 0xFFFFFF
const blue: Color = white & ~yellow; // 0x0000FF

并且由于这种枚举的用法存在于现实世界的代码中,因此对alter this behavior的重大改变。以及don't seem particularly inclined to try语言的维护者。

因此,无论好坏,数字枚举的类型大致都与number同义。


可以滚动您自己的更严格的枚举式对象,但是它涉及手工完成许多使用enum语法时自动发生的事情。这是一种可能的实现方式(不会给您reverse mapping):

namespace MyEnum {
  export const Zero = 0;
  export type Zero = typeof Zero;

  export const One = 1;
  export type One = typeof One;

  export const Two = 2;
  export type Two = typeof Two;
}
type MyEnum = typeof MyEnum[keyof typeof MyEnum];

const foo: MyEnum.Zero = 0 // okay
const bar: MyEnum.Zero = 1 // error!

它的工作原理如下:当您编写enum X { Y = 123, Z = 456 }时,TypeScript在运行时引入一个名为X的值,其属性为X.YX.Z。它还引入了名为XX.YX.Z类型。类型X.YX.Z只是值X.YX.Z的类型。但是类型X不是值X的类型。相反,它是属性类型X.Y | X.Z的并集。

我使用上面的namespaceexportconsttype来达到类似的效果。但是这里的区别在于,数字枚举的可分配性规则不适用,因此您可以进行严格的类型检查。

好的,希望能有所帮助;祝你好运!

Link to code

答案 1 :(得分:1)

如@jcalz所述,在数字枚举的情况下,Typescript不会区分numberMyEnum.X

任何数字都可以分配给数字枚举,但是对于字符串枚举则不是这样

那真是可悲...

基本上,这意味着您根本无法完全依靠MyEnum.Zero的“类型”。因此,您有两种选择:

次优解决方案1)使用该类型的变异版本

enum MyEnum {
    three = 3,
    four,
    five,
}

type EnumKeysToTrue = { [ P in MyEnum]: true }; // { 3: true; 4: true; 5: true; }
type TrueObject<T extends MyEnum> = Pick<EnumKeysToTrue, T>;

const a0: TrueObject<MyEnum.three> = { 3: true }; // OK, as expected
const a1: TrueObject<MyEnum.three> = { 4: true }; // Error
const a2: TrueObject<3> = { [MyEnum.three]: true }; // OK, as expected, just in case
const a3: TrueObject<4> = { [MyEnum.three]: true }; // Error, as expected, just in case

Playground Link

在这里,我们实际上是在存储一个对象,它的单个键是我们想要的枚举值,其值是true

这可能不是理想的,因为通过a{x}从这些Object.keys(a1)[0]变量中获取值可能不是首选方法;但是,它在某些情况下仍然可以使用。


次优解决方案2)使用枚举的值和分配的RHS的类型!

enum MyEnum {
    three = 3,
    four,
    five,
}

type EnumKeysToTrue = { [ P in MyEnum]: true }; // { 3: true; 4: true; 5: true; }
type Enumified<T extends number> = EnumKeysToTrue[T] extends true ? T : never;

const v0: Enumified<MyEnum.three> = MyEnum.four; // Error: Type 'MyEnum.four' is not assignable to type 'MyEnum.three'.ts(2322)
const v1: Enumified<MyEnum.three> = 4;  // Unfortunately, OK. So don't use it this way!
const v2: Enumified<4> = MyEnum.three;  // Type 'MyEnum.three' is not assignable to type '4'
const v3: Enumified<9> = 9;             // error: type 9 is not assignable to never
const v4: Enumified<4> = MyEnum.four;   // OK, as expected :)
let num: number = 9;
const v5: Enumified<typeof num> = MyEnum.four;  // Type 'MyEnum.four' is not assignable to type 'never'.ts(2322)

Playground Link

这可能会更好,因为一次可以获取v{x}变量的值并从那里开始! 请注意,必须将分配的值传递给Enumified<>,并且枚举必须在分配的RHS上,否则,就像上面的v1一样,它会无意中起作用


PS。您无法为代码库中的所有枚举概括EnumKEyToTrue。每个枚举实例必须为一个。