在 TypeScript 中,`number` 可以赋值给 `enum`,但 `string` 不可以。为什么?

huangapple go评论76阅读模式
英文:

In typescript, `number` can be assigned to `enum`, but `string` is not. why?

问题

在 TypeScript 中,number 可以赋值给 enum,但 string 不能...
看起来有点奇怪。为什么会这样工作呢?

是否有与此相关的 TypeScript 选项?

enum MixEnum {
  HELLO = 'hello',
  WORLD = 2,
}

const ms1: MixEnum = String('foo'); // 类型 'string' 不能赋值给类型 'MixEnum'。
const ms2: MixEnum = String('hello'); // 类型 'string' 不能赋值给类型 'MixEnum'。

const mn1: MixEnum = Math.random(); // 没有错误
const mn2: MixEnum = Number('99999'); // 没有错误
const mn3: MixEnum = MixEnum.WORLD + 100; // 没有错误

你可以在 TypeScript 中尝试这些代码:TypeScript Playground

英文:

In typescript, number can be assigned to enum, but string is not...
it looks weird. why works like this?

And is there some ts options related this?

enum MixEnum {
  HELLO = 'hello',
  WORLD = 2,
}

const ms1: MixEnum = String('foo'); // Type 'string' is not assignable to type 'MixEnum'.
const ms2: MixEnum = String('hello'); // Type 'string' is not assignable to type 'MixEnum'.

const mn1: MixEnum = Math.random(); // no error
const mn2: MixEnum = Number('99999'); // no error
const mn3: MixEnum = MixEnum.WORLD + 100; // no error

https://www.typescriptlang.org/play?#code/KYOwrgtgBAcpCi5oG8BQUoAl4BkcHkoBeKARgBp0oB1fAJRwBFioAmSgX1VQGMB7EAGcALlBAhSALlgIkLALIBDYQAsAdACdFIACZ8IACgCUAbl4CRYkK2lwIiSCzsAjYBoMByAJw+fH0+ZCouIAzNLyAJYAHg7QJHaxarQMzADUZAAMGWaoAPT5BYUF3KCOkTFyaBjYeIQkHirAADZNfB6UGMlMLOyoXIGWEBLh0bEKyupauvrGZvxBUEM2UOVj8ZCu7t6+Xv5zFqJDYSujciSrSEn03emkWTnzg4JSJxWOJADKwhoRIADmngAZnw2gFHodBMsLu8oF8fv9PI0WqCzEA

答案1

得分: 2

TypeScript 的数字枚举行为有一些奇怪之处。其中之一是,具有任何数字成员的枚举会将所有数字都视为成员,这是其中之一。另一个要注意的是反向映射,这会导致像 Object.keys(MyEnum) 这样的东西的类型与您通常预期的不同。

就个人而言,我建议避免使用 TypeScript 内置的 enums,因为会出现这种奇怪的情况。实际上,您可以使用带有 TypeScript 的 as const 的普通 JavaScript 对象创建自己的枚举,并使它们几乎以完全相同的方式运行。事实上,在这个特定的示例中,它们可能会表现得更好,因为 TypeScript 只会考虑您指定的具体数字作为枚举的成员。

制作自己的枚举而不使用 enum 关键字有优势和劣势:

优势:
+ 语法对于新接触 TypeScript 的 JavaScript 开发人员更加熟悉。
+ 生成的 JavaScript 更明显,因为只会移除 TypeScript 注释。
+ TypeScript 不会为数字枚举生成反向查找,这可能会令人困惑并导致意外问题。
+ TypeScript 不会将具有 number 类型的任何内容视为可能是数字枚举的成员。

劣势:
- TypeScript 不会为枚举生成关联的类型,因此您需要自己创建它,使代码略显冗长。
- 因为类型构造为常量类型的联合(例如 'a' | 'b'),所以您的 IDE 可能不会提供像直接建议通过枚举对象访问这些值的有用自动完成。
- 您始终需要为每个键指定一个值,而对于数字型 TypeScript 枚举,您可以省略初始值。

个人而言,我使用一个辅助的 EnumTypeOf 类型来处理手动构建类型的劣势。除了这个劣势外,我发现基于对象的枚举更容易使用和更直观。

type EnumTypeOf<T extends Record<string, unknown>> = T[keyof T];

const MixEnum = {
  HELLO: 'hello',
  WORLD: 2,
} as const;
type MixEnum = EnumTypeOf<typeof MixEnum>;

const ms1: MixEnum = String('foo'); // 类型 'string' 无法分配给类型 'MixEnum'。
const ms2: MixEnum = String('hello'); // 类型 'string' 无法分配给类型 'MixEnum'。

const mn1: MixEnum = Math.random(); // 类型 'number' 无法分配给类型 'MixEnum'。
const mn2: MixEnum = Number('99999'); // 类型 'number' 无法分配给类型 'MixEnum'。
const mn3: MixEnum = MixEnum.WORLD + 100; // 类型 'number' 无法分配给类型 'MixEnum'。

TypeScript Playground

英文:

TypeScript's number enums behave in a few strange ways. This behaviour, where an enum with any numeric member treats all numbers as though they are members, is one of them. Another to watch out for is reverse mappings, which can make the type of things like Object.keys(MyEnum) different to what you might normally expect.

Personally, I recommend avoiding TypeScript's built-in enums because of this sort of weirdness. It's possible to make your own enums using plain JavaScript objects with TypeScript's as const, and have them behave in almost exactly the same way. In fact, in this particular example they may behave better, because TypeScript will only consider the specific numbers you have specified to be members of your enum.

There are both upsides and downsides to making your own enums without the enum keyword:

+ The syntax is more familiar to JavaScript developers who are new to TypeScript

+ The generated JavaScript is more obvious, because only TypeScript annotations are removed

+ TypeScript won't generate reverse lookups for number enums, which can be unintuitive and cause unexpected problems

+ TypeScript won't treat anything with the type number as though it might be a member of a numeric enum

- TypeScript won't generate the type associated with an enum for you, so you need to create it yourself, making the code a bit more verbose

- Because the type is constructed as a union of constant types (e.g. &#39;a&#39; | &#39;b&#39;), you may not get as useful autocomplete in your IDE because it will suggest those values directly rather than suggesting you access them through your enum object

- You always need to specify a value for each key, whereas for numeric TypeScript enums you can omit the initialisers.


Personally, I use a utility EnumTypeOf type to assist with the downside of needing to construct a type manually. Aside from that downside, I find object-based enums easier and more intuitive to use.

type EnumTypeOf&lt;T extends Record&lt;string, unknown&gt;&gt; = T[keyof T];

const MixEnum = {
  HELLO: &#39;hello&#39;,
  WORLD: 2,
} as const;
type MixEnum = EnumTypeOf&lt;typeof MixEnum&gt;;

const ms1: MixEnum = String(&#39;foo&#39;); // Type &#39;string&#39; is not assignable to type &#39;MixEnum&#39;.
const ms2: MixEnum = String(&#39;hello&#39;); // Type &#39;string&#39; is not assignable to type &#39;MixEnum&#39;.

const mn1: MixEnum = Math.random(); // Type &#39;number&#39; is not assignable to type &#39;MixEnum&#39;.
const mn2: MixEnum = Number(&#39;99999&#39;); // Type &#39;number&#39; is not assignable to type &#39;MixEnum&#39;.
const mn3: MixEnum = MixEnum.WORLD + 100; // Type &#39;number&#39; is not assignable to type &#39;MixEnum&#39;.

TypeScript Playground

huangapple
  • 本文由 发表于 2023年7月10日 10:26:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/76650349.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定