对JS而言,枚举并不是一个单纯的类型,它会侵入 JS 运行时。
数字 Numeric enums
下面写法默认 name 为 1,age 为 2。使用的是时候直接读取即可。
enum Num {
name,
age
}
// name 为 0
const name = Num.name
也可以指定任意数字
enum Num {
name = 0,
age = 3
}
自增
数字枚举类型,只要指定第一个属性的值,后续会递增1。
enum Num{
first = 10,
second
}
字符串 String enums
在 debug 的过程中,由于 Number enums 只是单纯的数字,因此没有语义。字符串则有语义。
enum Str {
up = "UP",
down = "DOWN"
}
异构枚举 Heterogeneous enums
最好不要使用这种写法。
enum BooleanLikeHeterogeneousEnum { No = 0, Yes = "YES",}
计算与常成员 Computed and constant members
枚举成员的表达式如果是 TS 表达式的子类型,那么在 TS 编译时,其值可以完全被计算出来。
推荐的枚举成员表达式如下:
1. 字面量成员:数字或字符串
2. 对于 constant members 的引用,可以来自其他枚举类型
3. 带括号的 枚举成员表达式
4. 含有一元操作符+,-,~ 之一的表达式
5. 二院操作符:+
, -
, *
, /
, %
, <<
, >>
, >>>
, &
, |
, ^
6. 使用 NaN 或 Infinity 会报错
enum FileAccess{
// 常量
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
// 计算值
G = 123.length
}
联合枚举与枚举成员类型
字面量枚举成员是不可以被计算的。
当字面量枚举成员没有初始值,或者初始值是字符串,或数字,或带有一元操作符的数字字面量是,该成员会被认为是常量枚举成员。
边界情况
枚举成员用在 类型 中,同时又用在 值 中,会报错。
enum ShapeKind {
Circle,
Square
}
interface Circle {
kind: ShapeKind.Circle;
radius: number
}
interface Square {
kind: ShapeKind.Square;
sideLength: number
}
// crash
let c:Circle = {
// Type 'ShapeKind.Square' is not assignable to type 'ShapeKind.Circle'.
kind: ShapeKind.Square,
radius: 100
}
枚举类型会成为每个枚举成员的 集合 。因此类型系统清楚每一个在枚举类型的中的值。从而在比较枚举类型的 值 的时候会有错误提示。
enum E {
Foo,
Bar
}
function f(x: E){
if(x !== E.Foo || x !== E.Bar){
// This condition will always return 'true' since the types 'E.Foo' and 'E.Bar' have no overlap.
}
}
运行时中的枚举类型
在 JS 运行时中,枚举是真实存在的对象。
编译时中的枚举类型
可以用keyof typeof 获取枚举类型中的所有 key
enum LogLevel{
ERROR,
WARN,
INFO,
DEBUG
}
// 'ERROR' | 'WARN' | 'INFO' | 'DEBUG'
type LogLevelStrings = keyof typeof LogLevel
Reverse mappings
数字枚举成员需要创建 reverse mapping,即 枚举值 ---> key 的映射。
enum Enum{
A
}
let a = Enum.A
// A
let nameOfA = Enum[a]
编译成JS
'use strict'
var Enum;
(function (Enum){
// 这里做了个 值(0) ---> key 的映射
Enum[Enum["A"] = 0] = "A"
})(Enum || (Enum = {}))
let a = Enum.A
let nameOfA = Enum[a]
而 字符串成员 无须此操作。
enum Enum{
A = "string"
}
let a = Enum.A
// carsh 不能找到 key 为 string 的 枚举值
let nameOfA = Enum[a]
编译成JS
var Enum
(function (Enum){
Enum["A"] = "string";
})(Enum || (Enum = {}))
var a = Enum.A
var nameOfA = Enum[a]
const enums
通过 const 可以避免产生额外的代码 和 取值是多余的不必要访问。
const enum Enum{
A = 1,
B = A * 2
}
const emuns 只能用 constant 枚举表达式。在编译后,可以完全移除枚举相关的代码 。
const enum Direction { Up, Down, Left, Right,}let directions = [ Direction.Up, Direction.Down, Direction.Left, Direction.Right,];
/** @format */var Direction;(function (Direction) { Direction[Direction["Up"] = 0] = "Up"; Direction[Direction["Down"] = 1] = "Down"; Direction[Direction["Left"] = 2] = "Left"; Direction[Direction["Right"] = 3] = "Right";})(Direction || (Direction = {}));var directions = [ 0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */,];//# sourceMappingURL=index.js.map
当然,如果有 计算成员 就不能达到此效果。
下方中的 Right
/** @format */enum Enum{ A}const enum Direction { Up, Down, Left = Enum.A, Right = "123".length,}let directions = [ Direction.Up, Direction.Down, Direction.Left, Direction.Right,];
/** @format */var Enum;(function (Enum) { Enum[Enum["A"] = 0] = "A";})(Enum || (Enum = {}));var Direction;(function (Direction) { Direction[Direction["Up"] = 0] = "Up"; Direction[Direction["Down"] = 1] = "Down"; Direction[Direction["Left"] = 0] = "Left"; Direction[Direction["Right"] = "123".length] = "Right";})(Direction || (Direction = {}));var directions = [ 0 /* Up */, 1 /* Down */, 0 /* Left */, Direction.Right,];//# sourceMappingURL=index.js.map
Ambient enums
声明一个枚举,可以用来描述一个已经存在的枚举的形状。
delcare enum Enum{
A = 1,
B,
C = 2
}
non-ambient 与 ambient 枚举的区别是,没有初始值的普通的枚举成员如果其前一个枚举成员的值是常量,那么这个枚举成员的值也是常量。一个 没有初始值的ambient ,一个没有初始值的非常量的枚举成员,总会被认为是一个计算值。
也就是说,它并不会像const enums一样被干掉。
declare enum
declare enum Gender { Female, Male}console.log(Gender.Female)
生成的JS
运行报错,因为没有Gender 这个变量,欺骗了 TS
console.log(Gender.Female);//# sourceMappingURL=index.js.map
declare const enum
declare const enum Gender { Female, Male}console.log(Gender.Female)
生成的JS
console.log(0 /* Female */);//# sourceMappingURL=index.js.map
对象与枚举
对象使用类型推断为 const 可以替代枚举
const enum EDirection {
Up,
Down
}
const ODirection = {
Up: 0,
Down: 1
} as const
// (enum member) EDirection.Up = 0
EDirection.Up
// (property) Up: 0
ODirection.Up
// 用枚举作为参数
function walk(dir: EDirection){}
// 需要额外获取类型
type Direction = typeof ODirection[keyof typeof ODirection]
function run(dir: Direction){}
walk(EDirection.Left)
run(ODirection.Right)
枚举的优点
keep codebase aligned with state of JavaScript and when/if enums are added to JavaScript then you can move to the additional syntax