TypeScript的
枚举无法通过其他TypeScript代码以编程方式生成(无论如何,都需要使用某种源代码生成工具)。但是,您可以使用带有字符串/数字
字面类型的普通对象来模拟枚举的大部分功能,正如
TypeScript文档中所描述的那样。
所以,如果你从数组字面量开始,并且希望编译器跟踪元素的特定文字类型,那么你应该使用const
断言来要求这样做,否则编译器会推断string[]
,允许你添加/删除/重新排序元素。你不想添加/删除/重新排序元素,所以as const
表示你将数组字面量视为不可变的,因此它始终具有已知的长度、已知的元素和已知的顺序:
const countries = ['AF', 'AL', 'DZ', 'AS', 'AD', 'AO',
'AI', 'AQ', 'AG', ] as const;
现在你已经有足够的信息可以继续了。
在TypeScript中,枚举(Enums)同时为一个命名的
类型和一个命名的
值增加了定义。
类型是枚举值的类型,意味着它是所有可能枚举值类型的
联合。因此,我们可以通过使用
typeof countries
(使用
typeof
类型查询操作符获取其类型)来创建该类型,并用
number
进行索引(通过
索引访问),因为如果你有一个数组并用某个数值作为索引键,则会得到相应的元素:
type Country = (typeof countries)[number];
// type Country = "AF" | "AL" | "DZ" | "AS" | "AD" | "AO" |
// "AI" | "AQ" | "AG" /*...and so on*/
< p >
值 是你实际索引的对象,通过键来获取枚举值。在你的情况下,你希望键和值是相同的,所以我们可以使用
countries
数组来生成这样一个对象,并在类型系统中描述它的类型:
const Country = Object.fromEntries(
countries.map(v => [v, v])
) as { [K in Country]: K };
这里使用 Object.fromEntries()
方法从键值对数组组装一个对象,是通过将 countries
的每个元素映射map
到出现两次的一对中得到的。
至于类型,这是基于 Country
类型(即预期枚举值的联合)的映射类型,其中键是每个键 K
,值是相同类型 K
。
请注意,无论是
map()
还是
Object.fromEntries()
都没有足够强的类型推断能力,以便让TypeScript编译器推断出生成的值是否符合正确的形状,因此我使用了
type assertion来表示该值可以被视为该类型。
让我们同时看一下值和类型:
console.log(Country)
看起来不错!
现在你可以将 Country
当作 TypeScript 枚举类型来使用:
console.log(Country.AG) // "AG"
const c: Country = Country.AL
请注意,这与枚举并不完全相同。
实际上,枚举会创建一个完整的
命名空间
,其中枚举的每个键都作为类型导出,因此,例如,
Country.AL
也是一个引用
Country.AL
值类型的
类型。如果你想模拟这种情况,但不能以编程方式实现。
namespace Country {
export type AF = typeof Country.AF;
export type AL = typeof Country.AL;
export type DZ = typeof Country.DZ;
export type AS = typeof Country.AS;
export type AD = typeof Country.AD;
export type AO = typeof Country.AO;
export type AI = typeof Country.AI;
export type AQ = typeof Country.AQ;
export type AG = typeof Country.AG;
/* ...and so on */
}
那么您可以写
const d: Country.AL = Country.AL
但我怀疑这是否值得。
此外,enum
值类型被认为比其字面等价物更具体。使用我们模拟的枚举,我们可以编写:
const e: Country = "AI"; // okay
但是如果你有一个真正的
enum
,那么这将是一个错误,因为
"AI"
被认为不能赋值给
Country.AI
,即使它们在运行时具有相同的值。这个限制将防止某人意外地传递来自不同枚举的值,比如
enum Artist {
Picasso = "Picasso",
DaVinci = "Da Vinci",
Robot = "AI"
}
const f: Country = Artist.Robot;
在我们的情况下是可以接受的,因为Artist.Robot
可以赋值给"AI"
,而"AI"
是一个有效的Country
,但如果Country
是一个enum
,则会导致错误。
这些差异很小,对于许多用例来说,非enum
版本实际上更可取。但你仍然应该意识到它们。
播放代码的游乐场链接
z.enum()
方法,可以使用我已经有的数组(而不是使用z.nativeEnum()
方法)。但是我真的很希望能够得到关于你回答的更详细的解释。 - biorubenfs