yaoningvital / blog

my blog
31 stars 4 forks source link

TypeScript -- Enums #188

Open yaoningvital opened 4 years ago

yaoningvital commented 4 years ago

原文地址:http://www.typescriptlang.org/docs/handbook/enums.html 不错的学习枚举类型的文章: https://www.cnblogs.com/wjaaron/p/11672764.html https://www.cnblogs.com/xjy20170907/p/10882885.html

数字枚举(Numeric enums)

1、定义一个 enum

enum Direction {
    Up,
    Down,
    Left,
    Right
}

console.log('Direction:', Direction)
console.log('typeof Direction:', typeof Direction)
console.log('Object.getOwnPropertyNames(Direction):', Object.getOwnPropertyNames(Direction))

输出如下: image

Direction: {0: "Up", 1: "Down", 2: "Left", 3: "Right", Up: 0, Down: 1, Left: 2, Right: 3}
typeof Direction: object
Object.getOwnPropertyNames(Direction): (8) ["0", "1", "2", "3", "Up", "Down", "Left", "Right"]

2、使用

enum Response {
    No ,
    Yes,
}

function respond(recipient: string, message: Response): void {

}

respond('Princess Caroline', Response.Yes)

3、下面的定义会报错

enum Response {
    No = getSomeValue(),
    Yes, // TS1061: Enum member must have initializer.
}

字符串枚举(String enums)

举例

enum Response {
    Up = 'UP',
    Down = 'DOWN',
    Left = 'LEFT',
    Right = 'RIGHT',
}

1、字符串枚举没有自动增加的功能(数字枚举有这个功能);

异类枚举(Heterogeneous enums)

技术上,可以在枚举类型中混合使用数字和字符串,但是不推荐使用。

enum Response {
    No = 1,
    Yes = 'hello'
}

枚举类型的计算成员 和 常量成员

每一个枚举类型的成员可能有两种值,一个是计算值,一个是常量。

成员为 常量 的情形:

1、枚举类型中的第一个成员,并且没有被初始化。这种情况下它会被赋值 0 :

// E.X 是常量
enum E { X }

2、这个成员本身没有被初始化,并且它前面的成员是一个数字常量。这种情况下,当前成员的值是在前一个成员的值的基础上加 1 。

// E1 和 E2 中的所有成员都是常量
enum E1 { X, Y, Z }

enum E2 {
    A = 1, B, C
}

3、成员被一个 常量枚举表达式(constant enum expression)初始化。 常量枚举表达式 是TypeScript表达式的子集,能在编译时被完全编译。

一个表达式如果满足下面的一个条件,它就是一个 常量枚举表达式:

1、一个字面量枚举表达式(基本上就是一个字符串字面量 或者是 一个数字字面量); 2、对以前定义的 常量枚举成员的引用(可以来自不同的枚举); 3、带小括号的常量枚举表达式; 4、应用于常量枚举表达式的“+”、“-”、“~”一元运算符之一; 5、以常量枚举表达式作为操作数的这些二元运算符:+, -, *, /, %, <<, >>, >>>, &, |, ^ 。

如果常量枚举表达式计算为 NaN 或者 Infinity,编译时将报错。

所有其他的情况枚举成员都被认为是 计算成员

enum FileAccess {
    // constant members
    None,
    Read    = 1 << 1,
    Write   = 1 << 2,
    ReadWrite  = Read | Write,
    // computed member
    G = "123".length
}

联合枚举 及 枚举成员类型

有一个常量枚举成员的特别的子集,它们没有被计算进去:字面量枚举成员。一个字面量枚举成员 是一个常量枚举成员,它没有被初始化,或者值被下面的值初始化:

当一个枚举类型中的所有成员都是字面量枚举值,可以对其执行一些特殊的语法。

首先,枚举成员可以当做 类型 使用

我们可以指定一个成员它的类型是 一个枚举成员:

enum ShapeKind {
    Circle,
    Square,
}

interface Circle {
    kind: ShapeKind.Circle;
    radius: number;
}

interface Square {
    kind: ShapeKind.Square;
    sideLength: number;
}

let c: Circle = {
    kind: ShapeKind.Square, // TS2322: Type 'ShapeKind.Square' is not assignable to type 'ShapeKind.Circle'.  The expected type comes from property 'kind' which is declared here on type 'Circle'
    radius: 100
}

运行时的枚举类型

枚举类型是真正的对象,存在于运行时。比如下面的枚举:

enum E {
    X, Y, Z
}

它可以被传递给下面的函数:

enum E {
    X, Y, Z
}

function f(obj: { X: number }) {
    return obj.X
}

console.log(f(E))   // 0

编译时的枚举类型

尽管枚举类型是真实的对象,存在于运行时,但是如果对它执行 keyof 操作,它的表现会和你的预期不一样,不同于在普通的对象上执行 keyof 操作。

所以我们一般使用 keyof typeof 运算符来获得一个枚举类型的所有键,以字符串的形式返回。

enum LogLevel {
    ERROR, WARN, INFO, DEBUG
}

// 相当于:
// type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
type LogLevelStrings = keyof typeof LogLevel

function printImportant(key: LogLevelStrings, message: string) {
    const num = LogLevel[key]
    if (num <= LogLevel.WARN) {
        console.log('log level key is:', key)  // 'ERROR'
        console.log('log level value is:', num)  // 0
        console.log('log level message is:', message)  // 'this is a message'
    }
}

printImportant('ERROR', 'this is a message')

反向映射

除了用属性名作为一个成员添加了一个对象,数字枚举成员同样获得了一个从枚举值到枚举名称的反向映射。比如下面的例子:

enum Enum {
    A
}

let a = Enum.A;
let nameOfA = Enum[a]  // "A"

TypeScript会将上面的代码编译为:

var Enum;
(function (Enum) {
    Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
var a = Enum.A;
var nameOfA = Enum[a]; // "A"

如果将枚举的成员的值设置为了字符串,那么这个成员是没有反向映射的。

常量枚举(const enums)

在大多数情况下,enum 是一个完美有效的解决方案,但有时要求会更加苛刻。为了避免花掉额外的开销去生成代码,或者额外地迂回不直接地才能访问到一个枚举类型的值,我们可以使用 常量枚举(const enums)。const enum就是在定义枚举时在前面加上 const:

const enum Enum {
    A = 1,
    B = A * 2
}

1、上面的代码是原文中的例子,但是我在编辑器中输入上面的代码,TypeScript报错:“'const' enums are not supported ”,如下: image

typescript@3.7.5 是不是不支持 const enums 了这个问题还要再了解,现在暂不清楚。

下面来举个例子先说明 const enums 到底是什么意思。

看下面的代码:

enum Status {
    Off,
    On
}

const enum Animal {
    Dog,
    Cat
}

const status = Status.On
const animal = Animal.Dog

console.log('status:', status)
console.log('animal:', animal)

上面的代码经过TypeScript 的编译,编译为了下面的JavaScript:

"use strict";
exports.__esModule = true;
var Status;
(function (Status) {
    Status[Status["Off"] = 0] = "Off";
    Status[Status["On"] = 1] = "On";
})(Status || (Status = {}));
var status = Status.On;
var animal = 0 /* Dog */;
console.log('status:', status);
console.log('animal:', animal);

可以看到: 1、普通枚举类型 Status 被编译为了一个对象,它有 Off 属性,值为 0;有 0 属性,值为 "Off";有 On 属性,值为 1 ;还有 1 属性,值为 “On”; 2、而 常量枚举类型 Animal 根本没有出现在 编译后的代码中, 只出现了它的一个属性的值(Animal的Dog属性的值 0 替换了 Animal.Dog)。所以在TypeScript 编译过程中,会用到 const enum,但是编译完成后,就会完全移除它。 3、这样做可能是为了减少编译后的代码量,而之所以出现 const enum,只是为了在编写 .ts 时更加直观方便。

外部枚举(Ambient Enums)

外部枚举是使用 declare enum 定义的枚举类型:

declare enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Direction.Right ];

declare定义的类型只会用于编译时的检查,编译结果中会被删除。 上面的代码会被编译为:

"use strict";
exports.__esModule = true;
var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

外部枚举与声明语句一样,常出现在声明文件中。

同时使用 declare 和 const 也是可以的。

declare const enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]

上面的代码会被编译为:

"use strict";
exports.__esModule = true;
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];

如果只写const enum:

const enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]

它会被编译为:

"use strict";
exports.__esModule = true;
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];

跟上面写 declare const 编译的结果是一样的,所以 declare const 和 const 有什么区别呢?