在 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
它們彼此間的關係,如果我們用一張圖來表示,會類似像這樣:
特別留意上圖中的 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 協助筆者釐清與理解。
上面說明的是 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
https://tsplay.dev/w11MGw @ TypeScript Playground
它們的圈圈真的好亂
這圖要怎麼畫呢?
enum NUMERIC_GENDER {
MALE,
FEMALE,
}
type one = 0
// 可以很確定 one 和 number 不是同一種type!
// one 是 number 的子集
type T1 = one extends number ? true : false // true
type T2 = number extends one ? true : false // false
// 可以說 NUMERIC_GENDER.MALE 就是 one 嗎?
type T3 = one extends NUMERIC_GENDER.MALE ? true : false // true
type T4 = NUMERIC_GENDER.MALE extends one ? true : false // true
// 唉但是?
type T5 = NUMERIC_GENDER.MALE extends number ? true : false // true
type T6 = number extends NUMERIC_GENDER.MALE ? true : false // true