本文 AI 產出,尚未審核

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 驗證(如 zodio-ts)或 型別守衛
Mutable Tuple 被改變 預設 Tuple 為可變,誤改會破壞資料不變性。 使用 as constreadonly [T1, T2] 讓 Tuple 成為 readonly
Rest Tuple 與泛型衝突 在泛型函式中使用 Rest Tuple 時,推斷可能失敗。 明確指定 ...args: [...any[]] 或使用 overloads
Array 混用 把 Tuple 指派給 Array<T> 會失去固定長度的優勢。 若需要固定長度,保持 Tuple 型別;若需要彈性長度,改用 Array<T>

最佳實踐

  1. 先定義型別,再實作
    把 Tuple 型別抽離為 typeinterface,讓程式碼更具可讀性與重用性。

  2. 結合 as const
    在常量資料(如狀態列表、字串集合)上使用 as const,同時得到 readonly Tuple字面值聯合型別

  3. 適時使用可選與 Rest 元素

    • 前三個位置固定,之後不確定長度:[T1, T2, ...Trest[]]
    • 最後幾個位置可缺省:[T1, T2?, T3?]
  4. 加入執行期驗證
    當資料來源是外部(API、JSON)時,配合 schema 驗證(zod、yup)確保回傳符合 Tuple 結構。

  5. 避免過度使用 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: booleanerrorMessage: stringfieldName: string,使用 Tuple 可讓回傳值一次到位且型別安全。


總結

  • Tuple 是 TypeScript 為了彌補 JavaScript 陣列彈性而設計的「固定長度、位置型別」資料結構。
  • 透過 宣告、可選元素、Rest 元素,我們可以描述從簡單的二元組到複雜的可變長度集合。
  • 實務上,Tuple 在 API 回傳、座標計算、狀態機、CSV 產生等場景中提供了編譯期的安全檢查,減少了執行時錯誤。
  • 使用 as constreadonlyrun‑time 驗證,可以進一步提升 Tuple 的不可變性與可靠度。
  • 最後,記得根據需求選擇 TupleArray,不要濫用,保持程式碼的可讀性與維護性。

掌握了 Tuple,你的 TypeScript 程式碼將會更嚴謹易讀,也能在大型專案中減少許多因型別不一致而產生的隱性錯誤。祝你寫程式順利,Happy Coding! 🎉