目錄
enum是什麼
列舉(enum)是Typescript的一個功能,可用於定義
一系列被命名的常數
,其key常為upper camel case
列舉當作型別時,型別會是其值
enum Weekday {
Monday = 'Mon',
Tuesday = 'Tue',
Wednesday = 'Wed',
Thursday = 'Thur',
Friday = 'Fri',
Saturday = 'Sat'
}
const today:Weekday = Weekday.Monday;
// Type '"Tue"' is not assignable to type 'Weekday'.
const tomorrow:Weekday = 'Tue';
Numeric enum & String enum
Numeric enum
enum Direction {
Top = 1,
Bottom,
Left,
Right,
}
以這個例子來說Top被賦予初始值1,其餘的3個會自動依序+1
如果不想賦予Top初始值也可以,那它們就會從0開始依序+1
另外和string enum不同的點除了自動遞增的功能外,其內
的成員的值不必都是常數,常數與計算值可以並存
String enum
enum Fruit {
Apple = 'apple',
Banana = 'banana',
}
string enum的概念和numeric enum差不多,但可讀性較高
其內部的成員
必須賦予常數來初始化
,這個常數可以是string,也可以是另一個string enum的成員
Heterogeneous enum
enum Mix {
Up = 1,
Down = 'down'
}
雖然官方不推薦
這麼做,但其實一個enum裡可以同時有number和string
常數 & 計算值列舉(constant & computed enum)
常數
一個常數enum的成員
只能以constant enum expression
初始化
而constant enum expression需要符合以下任一條件
- 字串或數值
- 指向另一個常數enum的reference,且被指向的enum需要定義在前面
- 被括號包起來的constant enum expression (a parenthesized constant enum expression)
- 使用一元運算子(ex: +、-、~)做constant enum expression的運算 (unary operators applied to constant enum expression)
- 使用二進位運算子(ex: +、-、&、|、<<)做constant enum expression的運算 (binary operators with constant enum expressions as operands)
另外需要注意的是如果constant enum expression的值被判斷為NaN或Infinity
會報錯
enum Fruit {
// constant
Banana,
Orange = 1 << 1,
Tangerinr = 1 << 2,
Citrus = Orange | Tangerinr,
// computed
WaterMelon = 'watermelon'.length,
}
使用起來會像這樣
interface LunchBox {
main: 'rice' | 'spagetti',
fruit: Fruit.Citrus
}
const myLunchBox: LunchBox = {
main: 'rice',
// 會報錯Type 'Fruit.WaterMelon' is not assignable to type 'Fruit.Citrus'.
fruit: Fruit.WaterMelon
}
運行 & 編譯期間
運行期間
enum在運行時
可以視為物件
,因為Typescript被轉為Javascript後會把enum轉為物件,
它可以當做參數傳給函式
function foo(obj: {Banana: number}){
return obj.Banana;
}
// Fruit有Banana這個屬性,且型別為number,所以這樣是可以的
foo(Fruit);
編譯期間
前面說到在運行時enum
會被視為物件,只有在編譯時才會被視為型別
這一段我們來來看看typeof 與 keyof這兩個運算子一起用會怎樣
先來看看單獨使用keyof
keyof
是個只能在Typescript使用
的運算子,它能把物件型別的key抽出來
enum Fruit {
Orange = 'o',
Tangerinr = 't',
Citrus = 'c',
}
interface LunchBox {
main: 'rice' | 'spagetti',
fruit: Fruit.Citrus
}
// 相當於'main' | 'fruit'
type LunchContent = keyof LunchBox;
再來看看單獨使用typeof
type FruitKinds = typeof Fruit;
const c:FruitKinds = Fruit;
// Type 'Fruit' is not assignable to type 'typeof Fruit'
const d:FruitKinds = Fruit.Orange;
Type 'number' is not assignable to type 'typeof Fruit'
const e:FruitKinds = 1;
從以上我們可以得知typeof Fruit就是型別Fruit
,所以型別是FruitKinds的變數其值只能是Fruit
最後在來看看兩者合前我們先看一個例子,這個例子是抽出物件的key,然後把這些key變成一個型別
const student = {
name:'Alex',
studentId:'st1'
};
// 先用typeof student取得型別{name: string; studentId: string}
// 再來用keyof把物件型別的key抽出來
// 相當於'name' | 'studentId'
type StudentNeed = keyof typeof student;
const foo:StudentNeed = 'name';
const bar:StudentNeed = 'name';
// Type '"skill"' is not assignable to type '"name" | "studentId"'.
const fooBar:StudentNeed = 'skill';
接著來看合用
// 因為typeof Fruit就是型別Fruit
// 使用keyof把物件型別的key抽出來
// 所以相當於'Orange' | 'Tangerinr' | 'Citrus'
type FruitNames = keyof typeof Fruit;
const foo:FruitNames = 'Orange';
// Type '"Potato"' is not assignable to type '"Orange" | "Tangerinr" | "Citrus"'.
const bar:FruitNames = 'Potato';
那如果不使用typeof呢🤔
// Fruit會被視為物件,而一個物件底下有一些方法跟屬性(ex: toString, length)
type FruitNames = keyof Fruit;
const foo:FruitNames = 'toString';
const bar:FruitNames = 'slice';
// Type '"Orange"' is not assignable to type 'number | unique symbol | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | ... 28 more ... | "padEnd"'.
const fooBar:FruitNames = 'Orange';