詳解 TypeScript 枚舉類型

前言:

TypeScript 在 ES 原有類型基礎上加入枚舉類型,使得在 TypeScript 中也可以給一組數值賦予名字,這樣對開發者比較友好,可以理解枚舉就是一個字典。

枚舉類型使用enum來定義:

enum Day {
  SUNDAY,
  MONDAY,
  TUESDAY,
  WEDNESDAY,
  THURSDAY,
  FRIDAY,
  SATURDAY
 }

上面定義的枚舉類型的Day,它有7個值,TypeScript會為它們每個值分配編號,默認從0開始,在使用時,就可以使用名字而不需要記數字和名稱的對應關系瞭:

enum Day {
  SUNDAY = 0,
  MONDAY = 1,
  TUESDAY = 2,
  WEDNESDAY = 3,
  THURSDAY = 4,
  FRIDAY = 5,
  SATURDAY = 6
}

下面是將上面代碼轉譯為 JavaScript 後的效果:

var Day = void 0;
(function (Day) {
  Day[Day["SUNDAY"] = 0] = "SUNDAY";
  Day[Day["MONDAY"] = 1] = "MONDAY";
  Day[Day["TUESDAY"] = 2] = "TUESDAY";
  Day[Day["WEDNESDAY"] = 3] = "WEDNESDAY";
  Day[Day["THURSDAY"] = 4] = "THURSDAY";
  Day[Day["FRIDAY"] = 5] = "FRIDAY";
  Day[Day["SATURDAY"] = 6] = "SATURDAY";
})(Day || (Day = {}));

可以看到,每一個值都被賦予瞭對應的數字。

在TypeScript中,我們需要通過點的形式獲取枚舉集合中的成員:

console.log(Day.SUNDAY)   // 0
console.log(Day.MONDAY)   // 1

說完枚舉類型的基本使用,下面就來看一下常見的枚舉類型。

1. 數字枚舉

在上面的例子中,在僅指定常量命名的情況下,定義的就是一個默認從 0 開始遞增的數字集合,稱之為數字枚舉。如果想要從其他值開始遞增,可以將第一個值的索引值進行指定:

enum Color {
  Red = 2,
  Blue,
  Yellow
}
console.log(Color.Red, Color.Blue, Color.Yellow); // 2 3 4

可以對一個字段指定一個索引值,那他後面沒有指定索引值的就會依次加一:

// 指定部分字段,其他使用默認遞增索引
enum Status {
  Ok = 200,
  Created,
  Accepted,
  BadRequest = 400,
  Unauthorized
}
console.log(Status.Created, Status.Accepted, Status.Unauthorized); // 201 202 401

除此之外,還可以給每個字段指定不連續的任意索引值:

enum Status {
  Success = 200,
  NotFound = 404,
  Error = 500
}
console.log(Status.Success, Status.NotFound, Status.Error); // 200 404 500

數字枚舉在定義值時,可以使用計算值和常量。但是要註意,如果某個字段使用瞭計算值或常量,那麼該字段後面緊接著的字段必須設置初始值,這裡不能使用默認的遞增值瞭,來看例子:

// 初值為計算值
const getValue = () => {
  return 0;
};
enum ErrorIndex {
  a = getValue(),
  b, // error 枚舉成員必須具有初始化的值
  c
}
enum RightIndex {
  a = getValue(),
  b = 1,
  c
}
// 初值為常量
const Start = 1;
enum Index {
  a = Start,
  b, // error 枚舉成員必須具有初始化的值
  c
}

2. 字符串枚舉

TypeScript 將定義值是字符串字面量的枚舉稱為字符串枚舉,字符串枚舉值要求每個字段的值都必須是字符串字面量,或者是該枚舉值中另一個字符串枚舉成員:

// 使用字符串字面量
enum Message {
  Error = "Sorry, error",
  Success = "Hoho, success"
}
console.log(Message.Error); // 'Sorry, error'

// 使用枚舉值中其他枚舉成員
enum Message {
  Error = "error message",
  ServerError = Error,
  ClientError = Error
}
console.log(Message.Error); // 'error message'
console.log(Message.ServerError); // 'error message'

註意:這裡的其他枚舉成員指的是同一個枚舉值中的枚舉成員,因為字符串枚舉不能使用常量或者計算值,所以不能使用其他枚舉值中的成員。

3. 反向映射

定義枚舉類型的值時,可以通過 Enum['key'] 或者 Enum.key 的形式獲取到對應的值 valueTypeScript 還支持反向映射,但是反向映射隻支持數字枚舉,不支持字符串枚舉。

來看下面的例子:

enum Status {
  Success = 200,
  NotFound = 404,
  Error = 500
}
console.log(Status["Success"]); // 200
console.log(Status[200]); // 'Success'
console.log(Status[Status["Success"]]); // 'Success'

TypeScript 中定義的枚舉,編譯之後其實是一個對象,生成的代碼中,枚舉類型被編譯成一個對象,它包含瞭正向映射( name -> value)和反向映射( value -> name)。

下面來看看上面代碼中的 Status 編譯後的效果:

{
    200: "Success",
    404: "NotFound",
    500: "Error",
    Error: 500,
    NotFound: 404,
    Success: 200
}

可以看到,TypeScript 會把定義的枚舉值的字段名分別作為對象的屬性名和屬性值,把枚舉值的字段值分別作為對象的屬性值和屬性名,同時添加到對象中。這樣既可以通過枚舉值的字段名得到值,也可以通過枚舉值的值得到字段名。

4. 異構枚舉

異構枚舉就是枚舉值中成員值既有數字類型又有字符串類型,如下:

enum Result {
  Faild = 0,
  Success = "Success"
}

在開發過程中不建議使用異步枚舉。因為往往將一類值整理為一個枚舉值時,它們的特點是相似的。比如在做接口請求時的返回狀態碼,如果是狀態碼都是數值,如果是提示信息,都是字符串,所以在使用枚舉的時候,往往是可以避免使用異構枚舉的,主要是做好類型的整理。

5. 常量枚舉

TypeScript中,定義瞭枚舉值之後,編譯成 JavaScript 的代碼會創建一個對應的對象,這個對象可以在程序運行時使用。但是如果使用枚舉隻是為瞭讓程序可讀性好,並不需要編譯後的對象呢?這樣會增加一些編譯後的代碼量。TypeScript 中有一個const enum(常量枚舉),在定義枚舉的語句之前加上const關鍵字,這樣編譯後的代碼不會創建這個對象,隻是會從枚舉裡拿到相應的值進行替換:

enum Status {
  Off,
  On
}
const enum Animal {
  Dog,
  Cat
}
const status = Status.On;
const animal = Animal.Dog;

上面的代碼編譯成 JavaScript 之後是這樣的:

var Status;
(function(Status) {
  Status[(Status["Off"] = 0)] = "Off";
  Status[(Status["On"] = 1)] = "On";
})(Status || (Status = {}));
var status = Status.On;
var animal = 0; // Dog 

對於 Status 的處理,先是定義一個變量 Status,然後定義一個立即執行函數,在函數內給 Status 添加對應屬性,首先Status[“Off”] = 0是給Status對象設置Off屬性,並且值設為 0,這個賦值表達式的返回值是等號右邊的值,也就是 0,所以Status[Status[“Off”] = 0] = "Off"相當於Status[0] = “Off” 。創建瞭這個對象之後,將 Status 的 On 屬性值賦值給 status;再來看下 animal 的處理,編譯後的代碼並沒有像Status創建一個Animal對象,而是直接把Animal.Dog的值0替換到瞭const animal = Animal.Dog表達式的Animal.Dog位置。

通過定義常量枚舉,可以以清晰、結構化的形式維護相關聯的常量集合。而且因為轉譯後抹除瞭定義、內聯成員值,所以在代碼的體積和性能方面並不會比直接內聯常量值差。

6. 枚舉成員類型和聯合枚舉類型

如果枚舉值裡所有成員都是字面量類型的值,那麼枚舉的每個成員和枚舉值本身都可以作為類型來使用,我們稱這樣的枚舉成員為字面量枚舉成員。

滿足條件的枚舉成員的值有以下三種:

  • 沒有初始值的枚舉成員,例如:enum E { A }
  • 值為字符串字面量,例如:enum E { A = 'a' }
  • 值為數值字面量,或者帶有-符號的數值字面量,例如:enum E { A = 1 }、enum E { A = -1 }

(1)枚舉成員類型

當所有枚舉成員都擁有字面量枚舉值時,就枚舉成員成為瞭類型:

enum Animal {
  Dog = 1,
  Cat = 2
}

interface Dog {
  type: Animal.Dog; 
}
interface Cat {
  type: Animal.Cat; 
}

let cat: Cat = {
  type: Animal.Dog // error [ts] 不能將類型“Animal.Dog”分配給類型“Animal.Cat”
};
let dog: Dog = {
  type: Animal.Dog
};

可以看到,代碼的第七行使用Animal.Dog作為類型,指定接口Dog的必須有一個type字段,且類型為Animal.Dog

(2)聯合枚舉類型

當枚舉值符合條件時,這個枚舉值就可以看做是一個包含所有成員的聯合類型:

enum Status {
  Off,
  On
}
interface Light {
  status: Status;
}
enum Animal {
  Dog = 1,
  Cat = 2
}
const light1: Light = {
  status: Animal.Dog // error 不能將類型“Animal.Dog”分配給類型“Status”
};
const light2: Light = {
  status: Status.Off
};
const light3: Light = {
  status: Status.On
};

上面例子定義接口 Light status 字段的類型為枚舉值 Status,那麼此時 status 的屬性值必須為 Status.Off 和 Status.On 中的一個,也就是相當於status: Status.Off | Status.On

7. 枚舉合並

說完常見的枚舉類型,最後來看看枚舉合並的概念。對於枚舉類型的值,我們可以分開進行聲明:

enum Day {
  SUNDAY,
  MONDAY,
  TUESDAY
 }

enum Day {
  WEDNESDAY,
  THURSDAY,
  FRIDAY,
  SATURDAY
 }

這時 TypeScript 就會對這個枚舉值進行合並操作,合並後編譯為JavaScript的代碼如下:

var Day = void 0;
(function (Day) {
  Day[Day["SUNDAY"] = 0] = "SUNDAY";
  Day[Day["MONDAY"] = 1] = "MONDAY";
  Day[Day["TUESDAY"] = 2] = "TUESDAY";
  Day[Day["WEDNESDAY"] = 3] = "WEDNESDAY";
  Day[Day["THURSDAY"] = 4] = "THURSDAY";
  Day[Day["FRIDAY"] = 5] = "FRIDAY";
  Day[Day["SATURDAY"] = 6] = "SATURDAY";
})(Day || (Day = {}));

到此這篇關於詳解 TypeScript 枚舉類型的文章就介紹到這瞭,更多相關TypeScript 枚舉類型內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: