【TypeScript】 Day8 閱讀和實作「元組」和「列舉」。
前言
元組
定義:數組合併了相同型別的物件,而元組(Tuple)合併了不同型別的物件。
乍看之下,我覺得非常難懂,所以請 ChatGPT 提供解釋跟舉例,
在 TypeScript(TS)中,數組(Array) 和 元組(Tuple) 的主要區別在於它們的型別約束方式:
1. 數組(Array)
數組是一組相同型別的元素的集合。例如:
let numbers: number[] = [1, 2, 3, 4]; // 數組中只有數字
let names: string[] = ["Alice", "Bob", "Charlie"]; // 數組中只有字串
即使使用泛型 Array<T>,數組的元素仍然是相同型別的:
let mixed: Array<number | string> = [1, "two", 3, "four"];
在這個例子中,數組允許 number 或 string,但它並不要求某個索引一定是特定型別,所有元素都是可變的。
2. 元組(Tuple)
元組則是一組不同型別的值,並且它的長度和每個索引的型別都是固定的。例如:
let person: [string, number] = ["Alice", 25];
這個元組:
- 第一個元素 必須是
string - 第二個元素 必須是
number
如果你試圖改變其中某個值的型別,TypeScript 會報錯:
person[0] = 30; // ❌ 錯誤,因為第一個元素應該是 string
person[1] = "Alice"; // ❌ 錯誤,因為第二個元素應該是 number
但如果用數組,就不會有這種約束,例如 Array<string | number>,它不會限制某個索引的位置必須是特定型別。
3. 數組 vs 元組 匯合的概念
你的問題提到:
數組合併了相同型別的物件,而元組合併了不同型別的物件。
這指的是:
- 數組 是一組相同型別的元素,比如
string[]或number[]。 - 元組 是一組不同型別的元素,比如
[string, number],每個位置的型別不同。
✅ 例子:
// 數組:合併相同型別的物件
let users: { name: string; age: number }[] = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 }
]; // 所有物件的型別都相同
// 元組:合併不同型別的物件
let userTuple: [string, number] = ["Alice", 25]; // 這是一組固定長度、不同型別的值
總結:
- 數組適用於同型別的元素的集合,元素的長度不限,型別一致。
- 元組適用於不同型別的元素的組合,長度固定,每個位置的型別不同。
所以,當元組中的元素超過指定的長度組合,例如他在陣列中使用 push 方法,多出來的元素就會被限制為元組中每個型別的聯合型別。
let tuple: [string, number] = ["Alice", 25];
// 可以正常存取元組內的元素:
console.log(tuple[0]); // "Alice"
console.log(tuple[1]); // 25
// 超出元組長度時,TypeScript 限制新增的元素型別為 元組內型別的聯合型別
tuple.push("Hello"); // ✅ 允許,因為 "Hello" 屬於 (string | number)
tuple.push(42); // ✅ 允許,因為 42 屬於 (string | number)
tuple.push(true); // ❌ 錯誤,因為 boolean 不是 (string | number)
越界後的存取
即使我們能用 .push() 新增元素,TypeScript 仍然不允許透過索引存取越界的元素,因為元組的索引型別是固定的。
console.log(tuple[2]); // ❌ 報錯,因為 TypeScript 無法確認 tuple[2] 的型別
這是因為 TypeScript 仍然認為 tuple 應該是 [string, number],而 tuple[2] 超出了定義範圍,所以會報錯。
如果要讓 TypeScript 允許動態存取,可以把元組變成普通數組:
let dynamicTuple: (string | number)[] = tuple; // 這樣就可以存取任意索引
console.log(dynamicTuple[2]); // ✅ 不會報錯
列舉
列舉(Enum)型別用於取值被限定在一定範圍內的場景,比如一週只能有七天,顏色限定為紅綠藍等。
1. 基本列舉
最基本的 enum 用法如下:
enum Direction {
Up,
Down,
Left,
Right
}
console.log(Direction.Up); // 0
console.log(Direction.Down); // 1
console.log(Direction.Left); // 2
console.log(Direction.Right); // 3
說明:
Direction.Up會被賦值0,Direction.Down是1,依此類推。- 預設情況下,第一個成員的值是
0,後續成員的值會自動遞增。
2. 手動設定值
你可以手動指定成員的數值:
enum Status {
Success = 200,
NotFound = 404,
ServerError = 500
}
console.log(Status.Success); // 200
console.log(Status.NotFound); // 404
這樣可以讓 enum 更加直觀地對應一些業務邏輯,比如 HTTP 狀態碼。
3. 異構列舉(混合數值 & 字串)
enum 也允許數字和字串混合:
enum ResponseStatus {
Success = 200,
Error = "ERROR",
NotFound = 404
}
console.log(ResponseStatus.Success); // 200
console.log(ResponseStatus.Error); // "ERROR"
這種做法在大多數情況下不建議,因為它會讓列舉變得難以處理。
4. 字串列舉
你可以使用字串列舉來確保值的唯一性,不會被數字隱式遞增影響:
enum Colors {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
console.log(Colors.Red); // "RED"
console.log(Colors.Green); // "GREEN"
console.log(Colors.Blue); // "BLUE"
字串列舉不會有數值型列舉的自動遞增行為,因此更加穩定。
5. 反向對應
對於數字型 enum,TypeScript 會自動建立反向對應:
enum Role {
User = 1,
Admin = 2
}
console.log(Role.User); // 1
console.log(Role[1]); // "User"
但對於字串型 enum,不會有反向映射!
6. 常數列舉 (const enum)
如果你不需要 enum 在編譯後還保留對象的形式,可以使用 const enum 來提升效能:
const enum Size {
Small = 1,
Medium = 2,
Large = 3
}
console.log(Size.Small); // 1
編譯後的結果:
console.log(1 /* Small */);
它不會產生 enum 物件,而是直接內嵌數值,提高效能。
7. declare enum 的用法
當你使用 declare enum 時,TypeScript 只會將其視為類型信息,而不會在編譯後生成真正的 enum 定義。例如:
declare enum ExternalEnum {
A = 1,
B = 2,
C = 3
}
let value: ExternalEnum = ExternalEnum.A;
console.log(value); // 這裡可以正常使用
特點:
declare enum不會被編譯成 JavaScript,它只是用來告訴 TypeScript 這個enum已經存在。- 這通常用於描述已經存在的 JavaScript 代碼或第三方庫提供的
enum。
declare enum 在編譯後的行為
假設我們有這個 declare enum:
declare enum Colors {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
console.log(Colors.Red);
編譯後的 JavaScript 會變成:
console.log(Colors.Red);
你會發現 Colors 並沒有被定義!
這是因為 declare enum 只在 TypeScript 內部提供類型提示,但不會影響最終的 JavaScript 代碼。
何時使用 declare enum?
declare enum 通常用於:
- 對應 JavaScript 全局變數:
- 如果某個
enum是從 JavaScript 文件或CDN加載的,你可以用declare enum讓 TypeScript 知道它的存在,但不會生成重複的定義。
- 如果某個
- 與
.d.ts聲明檔一起使用:- 當你寫 TypeScript 的
.d.ts聲明文件來描述一個 JavaScript 庫時,你可以使用declare enum來聲明enum。
- 當你寫 TypeScript 的
例如,假設有個 JavaScript 庫:
var Status = {
Success: 200,
NotFound: 404
};
你可以寫一個 TypeScript 聲明檔:
declare enum Status {
Success = 200,
NotFound = 404
}
這樣 TypeScript 在開發時會提供補全與類型檢查,但最終不會影響 JavaScript 代碼。
declare enum vs const enum
| 類型 | 作用 |
|---|---|
enum |
會生成 JavaScript 物件 |
const enum |
內嵌數值,不生成 enum 物件,提高效能 |
declare enum |
不會生成 JavaScript 代碼,純類型提示 |
示例對比:
enum NormalEnum {
A = 1,
B = 2
}
const enum ConstEnum {
X = 10,
Y = 20
}
declare enum DeclaredEnum {
P = 100,
Q = 200
}
console.log(NormalEnum.A); // ✅ 會生成 JavaScript 物件
console.log(ConstEnum.X); // ✅ 直接內嵌數值
console.log(DeclaredEnum.P); // ❌ 編譯後的 JS 可能會報錯,因為它沒有真正的定義
總結
declare enum不會影響最終的 JavaScript 代碼,它只是告訴 TypeScript 有這個enum存在。- 適用於 聲明外部 JavaScript 庫 或 在
.d.ts文件中使用。 - 如果
enum需要提升效能(消除 JavaScript 物件),使用const enum。 - 如果
enum需要完整保留,使用一般enum。