TypeScript学习(十三):枚举类型

583 阅读3分钟

对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  不能找到 keystring 的 枚举值
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

参考

www.typescriptlang.org/docs/handbo…

zhuanlan.zhihu.com/p/125889229