跳至主要内容

[Day23] TS:談談讓人又愛又恨的 enum

enum

在 TypeScript 中,enum 算是還蠻常會使用到的型別,但有時如果用的不好或觀念不夠清楚的話,就會有點痛苦而不知所以然。

以下面這個 GENDER 來說:

enum GENDER {
MALE = 'male',
FEMALE = 'female',
}

我們知道使用 GENDER.MALE 可以取得 "male",所以有時侯會直覺的想說,那 "male" 應該算是 GENDER 的子集合吧!?而這也是筆者在使用 enum 時很容易忽略或犯的錯誤。

讓我們來做一點小測驗,讀者可以想想看,下面這些條件會是 true 或 false:

type T1 = GENDER extends string ? true : false;
type T2 = string extends GENDER ? true : false;

type T3 = number extends GENDER ? true : false;

type T4 = 'male' extends GENDER ? true : false;
type T5 = GENDER.MALE extends GENDER ? true : false;
type T6 = GENDER.MALE extends string ? true : false;

這裡的重點是要區分清楚 "male""female"GENDER(enum) 和 string 它們彼此間的關係,如果我們用一張圖來表示,會類似像這樣:

Screen Shot 2021-10-08 at 11.39.11 PM

特別留意上圖中的 GENDER.MALE"male" 的部分。

發現了嗎?雖然說我們知道,透過 GENDER.MALE 可以取得 "male",但實際上 "male" 並不是 GENDER 這個 enum 的子集合!更具體來說,從值的角度思考時 GENDER.MALE"male" 是一樣的,但從型別的角度思考是 GENDER.MALE"male" 互不是彼此的子集合,這個概念真的蠻重要的。上面提供的程式碼,便是用 Conditional Type 的方式來驗證這樣的關係:

enum STRING_GENDER {
MALE = 'male',
FEMALE = 'female',
}

type T1 = STRING_GENDER extends string ? true : false; // true
type T2 = string extends STRING_GENDER ? true : false; // false
type T3 = string extends STRING_GENDER.MALE ? true : false; // false
type T4 = 'male' extends STRING_GENDER ? true : false; // false
type T5 = 'male' extends STRING_GENDER.MALE ? true : false; // false
type T6 = STRING_GENDER.MALE extends STRING_GENDER ? true : false; // true
type T7 = STRING_GENDER.MALE extends string ? true : false; // true
type T8 = STRING_GENDER extends STRING_GENDER.MALE ? true : false; // false

另外,雖然 GENDER.MALE"male" 彼此之間沒有從屬關係外,但它們都還是屬於型別 string 的子集合。

上面這個部分要謝謝同事 Peter 協助筆者釐清與理解。

不能直接類推的 Numeric enums

上面說明的是 string enums 的情況,也就是 enum 的 value 是 string 時,但另外常用的 enum 還有 numeric enums 這時候在集合的表現上會有所不同,有興趣的讀者可以再自行嘗試看:

enum NUMERIC_GENDER {
MALE,
FEMALE,
}

type T11 = NUMERIC_GENDER extends number ? true : false; // true
type T12 = number extends NUMERIC_GENDER ? true : false; // true
type T13 = number extends NUMERIC_GENDER.MALE ? true : false; // true
type T14 = 10 extends NUMERIC_GENDER ? true : false; // true
type T15 = 10 extends NUMERIC_GENDER.MALE ? true : false; // true
type T16 = NUMERIC_GENDER.MALE extends NUMERIC_GENDER ? true : false; // true
type T17 = NUMERIC_GENDER.MALE extends number ? true : false; // true
type T18 = NUMERIC_GENDER extends NUMERIC_GENDER.MALE ? true : false; // false
type T19 = NUMERIC_GENDER.MALE extends NUMERIC_GENDER.FEMALE ? true : false; // false

關於 numeric enums 和 string enums 的情況不太一樣,這點未來如果有遇到,可能要在多加留意。

範例程式碼

https://tsplay.dev/w11MGw @ TypeScript Playground

參考資料

  • 同事
  • enums @ TypeScript