TypeScript 教學:陣列與集合(Arrays & Collections)— Tuple(固定長度陣列)
簡介
在 JavaScript 中,陣列(Array)是一個「可變長度」的資料結構,裡面的元素型別也可以不一致。雖然這種彈性在前端開發中相當方便,但在大型專案或是需要資料結構嚴謹的情境下,隨意的陣列會帶來型別錯誤、維護成本升高等問題。
TypeScript 為了彌補這一點,除了提供一般的 Array<T>,還加入了 Tuple(固定長度陣列) 的概念。Tuple 允許我們在編譯階段就確定每個位置的型別與長度,讓程式在執行前就能捕捉到許多潛在的錯誤。對於 API 回傳值、函式參數、資料庫模型 等需要明確結構的情境,Tuple 是一個不可或缺的工具。
本篇文章將從 概念說明、實作範例、常見陷阱、最佳實踐,一路帶你深入了解 Tuple,並提供實務上可直接套用的範例,讓你從「初學者」升級為「中級開發者」的實力。
核心概念
1. 什麼是 Tuple?
在 TypeScript 中,Tuple 是一種固定長度且每個位置都有指定型別的陣列。語法上以方括號 [] 包住型別列表,例如:
let point: [number, number] = [10, 20];
此 point 必須恰好有兩個元素,且第一個元素必須是 number,第二個也必須是 number。如果多或少一個元素,或是型別不符合,編譯器會直接報錯。
2. Tuple 與一般陣列的差異
| 特性 | 一般陣列 Array<T> |
Tuple |
|---|---|---|
| 長度 | 可變(任意長度) | 固定(編譯時決定) |
| 每個位置型別 | 同一個 T |
每個位置可不同型別 |
| 讀寫安全性 | 只能保證元素是 T |
每個位置都有明確型別,安全性更高 |
| 常見用途 | 需要動態增減元素的清單 | API 回傳值、座標、鍵值對等固定結構 |
3. 基本語法與型別推斷
3.1 宣告與初始化
// 完全指定
let userInfo: [string, number, boolean] = ["Alice", 28, true];
// 依賴型別推斷(若直接賦值,TS 會自動推斷為 Tuple)
let coordinates = [0, 0] as const; // 推斷為 readonly [0, 0]
// 使用 `as const` 讓陣列變成 readonly Tuple
const status = ["success", 200] as const; // type: readonly ["success", 200]
3.2 讀取與寫入
let pair: [string, number] = ["TS", 2025];
const name = pair[0]; // string
const year = pair[1]; // number
pair[0] = "TypeScript"; // ✅ 合法
pair[1] = 2024; // ✅ 合法
// pair[2] = true; // ❌ 編譯錯誤:索引超出範圍
3.3 可選元素(Optional Elements)
Tuple 允許最後幾個元素設為可選:
type Response = [number, string?, boolean?];
let r1: Response = [200];
let r2: Response = [404, "Not Found"];
let r3: Response = [500, "Server Error", true];
3.4 Rest 元素(Rest Elements)
若需要「固定前幾個」加上一段不定長度的元素,可以使用 Rest 元素:
type LogEntry = [Date, string, ...any[]];
const log1: LogEntry = [new Date(), "User login"];
const log2: LogEntry = [new Date(), "Data received", { id: 1 }, { id: 2 }];
4. Tuple 與介面(Interface)/類別(Class)的結合
Tuple 本身只能描述純資料結構,但可與介面或類別結合,形成更完整的型別系統。
interface PersonTuple {
info: [string, number]; // name, age
address: [string, string]; // city, country
}
const john: PersonTuple = {
info: ["John", 30],
address: ["Taipei", "Taiwan"]
};
程式碼範例
以下提供 5 個實用範例,涵蓋從基礎到進階的應用情境,並在每段程式碼旁加上說明註解。
範例 1️⃣:函式參數的 Tuple
/**
* 計算兩個座標點之間的曼哈頓距離(|x1 - x2| + |y1 - y2|)
* 使用 Tuple 讓每個座標的型別與長度固定
*/
function manhattanDistance(p1: [number, number], p2: [number, number]): number {
const [x1, y1] = p1; // 解構賦值,取得 x1、y1
const [x2, y2] = p2;
return Math.abs(x1 - x2) + Math.abs(y1 - y2);
}
// 呼叫
const d = manhattanDistance([3, 5], [9, 1]); // 10
重點:使用 Tuple 作為參數,可在編譯階段即防止傳入錯誤長度或型別的陣列。
範例 2️⃣:API 回傳值的型別安全
假設後端提供 /api/user/:id,回傳資料結構固定為 [id, name, isActive]。
type UserInfo = [number, string, boolean];
/**
* 取得使用者資訊,回傳 Tuple
*/
async function fetchUser(id: number): Promise<UserInfo> {
const response = await fetch(`/api/user/${id}`);
const data = await response.json(); // 假設回傳的 JSON 為 [id, name, isActive]
return data as UserInfo; // 斷言為 Tuple
}
// 使用
fetchUser(1).then(([uid, uname, active]) => {
console.log(`User #${uid}: ${uname} (${active ? "online" : "offline"})`);
});
提示:若後端回傳的資料結構不符合 Tuple,編譯不會警告,但執行時會出錯。可搭配 run‑time validation(例如
zod)提升安全性。
範例 3️⃣:使用 Rest Tuple 來處理不定長度參數
/**
* 建立一個簡易的 CSV 產生器
* 前兩個參數固定為欄位名稱,之後的參數可任意多筆資料
*/
function csvBuilder(
headers: [string, string],
...rows: Array<[string, string]>
): string {
const headerLine = headers.join(",");
const rowLines = rows.map(r => r.join(",")).join("\n");
return `${headerLine}\n${rowLines}`;
}
// 呼叫
const csv = csvBuilder(
["name", "email"],
["Alice", "alice@example.com"],
["Bob", "bob@example.com"]
);
console.log(csv);
/*
name,email
Alice,alice@example.com
Bob,bob@example.com
*/
說明:
...rows: Array<[string, string]>表示「任意多筆」二元組(Tuple),非常適合 CSV、表格等固定欄位的資料。
範例 4️⃣:結合可選元素與解構賦值
type ApiResult = [boolean, string?, Error?];
/**
* 處理 API 結果,根據不同長度做不同邏輯
*/
function handleResult(res: ApiResult) {
const [ok, message, err] = res;
if (ok) {
console.log("成功:", message ?? "無訊息");
} else {
console.error("失敗:", err?.message ?? "未知錯誤");
}
}
// 測試
handleResult([true, "資料已儲存"]);
handleResult([false, undefined, new Error("網路錯誤")]);
技巧:使用可選元素時,解構賦值仍能取得
undefined,因此需要自行檢查是否存在。
範例 5️⃣:Tuple 與 as const 的結合(Readonly Tuple)
// 使用 as const 讓陣列變成 immutable Tuple
const STATUS = ["idle", "loading", "success", "error"] as const;
// 產生對應的聯合型別
type Status = typeof STATUS[number]; // "idle" | "loading" | "success" | "error"
function setStatus(s: Status) {
console.log(`目前狀態: ${s}`);
}
// 正確使用
setStatus("loading");
// 錯誤示範(編譯時會報錯)
// setStatus("unknown"); // ❌
重點:
as const把普通陣列升級為 readonly Tuple,同時產生字面值型別,適合實作 enum 替代方案。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
| 索引超出範圍 | 直接使用 tuple[2] 時,若 Tuple 長度只有 2,會編譯錯誤。 |
確認 Tuple 定義的長度,或使用 可選元素 ([T?, ...]) |
型別寬鬆的斷言 (as any / as Tuple) |
把任何陣列斷言為 Tuple 會失去型別安全。 | 盡量使用 run‑time 驗證(如 zod、io-ts)或 型別守衛 |
| Mutable Tuple 被改變 | 預設 Tuple 為可變,誤改會破壞資料不變性。 | 使用 as const 或 readonly [T1, T2] 讓 Tuple 成為 readonly |
| Rest Tuple 與泛型衝突 | 在泛型函式中使用 Rest Tuple 時,推斷可能失敗。 | 明確指定 ...args: [...any[]] 或使用 overloads |
與 Array 混用 |
把 Tuple 指派給 Array<T> 會失去固定長度的優勢。 |
若需要固定長度,保持 Tuple 型別;若需要彈性長度,改用 Array<T> |
最佳實踐
先定義型別,再實作
把 Tuple 型別抽離為type或interface,讓程式碼更具可讀性與重用性。結合
as const
在常量資料(如狀態列表、字串集合)上使用as const,同時得到 readonly Tuple 與 字面值聯合型別。適時使用可選與 Rest 元素
- 前三個位置固定,之後不確定長度:
[T1, T2, ...Trest[]] - 最後幾個位置可缺省:
[T1, T2?, T3?]
- 前三個位置固定,之後不確定長度:
加入執行期驗證
當資料來源是外部(API、JSON)時,配合 schema 驗證(zod、yup)確保回傳符合 Tuple 結構。避免過度使用 Tuple
Tuple 的優勢在於固定結構,若資料本身是可變長度或型別不固定,使用普通Array<T>會更自然。
實際應用場景
| 場景 | 為何使用 Tuple | 範例 |
|---|---|---|
| 座標系統(2D/3D) | 固定長度、型別一致 | [number, number]、[number, number, number] |
| API 回傳值(固定欄位) | 保證欄位順序與型別 | [id: number, name: string, isActive: boolean] |
| 狀態機(狀態 + 事件) | 兩個以上固定資訊 | [State, Event] |
| 表格列資料 | 每列欄位固定,型別多樣 | [string, number, Date] |
| 函式多返回值 | 需要同時回傳多個不同型別的結果 | function split(str: string): [string, string] |
案例:在前端開發中,常見的 表單驗證結果 會同時回傳
isValid: boolean、errorMessage: string、fieldName: string,使用 Tuple 可讓回傳值一次到位且型別安全。
總結
- Tuple 是 TypeScript 為了彌補 JavaScript 陣列彈性而設計的「固定長度、位置型別」資料結構。
- 透過 宣告、可選元素、Rest 元素,我們可以描述從簡單的二元組到複雜的可變長度集合。
- 實務上,Tuple 在 API 回傳、座標計算、狀態機、CSV 產生等場景中提供了編譯期的安全檢查,減少了執行時錯誤。
- 使用
as const、readonly、run‑time 驗證,可以進一步提升 Tuple 的不可變性與可靠度。 - 最後,記得根據需求選擇 Tuple 或 Array,不要濫用,保持程式碼的可讀性與維護性。
掌握了 Tuple,你的 TypeScript 程式碼將會更嚴謹、易讀,也能在大型專案中減少許多因型別不一致而產生的隱性錯誤。祝你寫程式順利,Happy Coding! 🎉