TypeScript 陣列與元組 (Array and Tuple)

陣列 (Array) 和元組 (Tuple) 是 TypeScript 中用來儲存多個值的資料結構。讓我們來了解如何在 TypeScript 中使用它們。

陣列 (Array)

陣列用來儲存相同型別的多個值。

陣列型別標註

有兩種方式可以標註陣列型別:

// 方式一:型別[] (推薦)
let numbers: number[] = [1, 2, 3, 4, 5];
let names: string[] = ['Alice', 'Bob', 'Charlie'];
let flags: boolean[] = [true, false, true];

// 方式二:Array<型別> (泛型語法)
let scores: Array<number> = [90, 85, 92];
let words: Array<string> = ['hello', 'world'];

兩種寫法功能完全相同,選擇你喜歡的風格即可。

空陣列

宣告空陣列時,必須指定型別,否則 TypeScript 會推論為 any[]

// 明確指定型別
let items: string[] = [];
items.push('item1'); // OK
// items.push(123);   // 錯誤:number 不能指派給 string

// 沒有指定型別,推論為 never[] 或 any[]
let unknown = []; // 在 strict 模式下會有問題

唯讀陣列

使用 readonly 關鍵字或 ReadonlyArray<T> 來建立唯讀陣列:

// 使用 readonly 關鍵字
let readonlyNumbers: readonly number[] = [1, 2, 3];
// readonlyNumbers.push(4);     // 錯誤:push 不存在於 readonly number[]
// readonlyNumbers[0] = 10;     // 錯誤:無法指派給 readonly 屬性

// 使用 ReadonlyArray<T>
let readonlyNames: ReadonlyArray<string> = ['Alice', 'Bob'];

陣列方法的型別

TypeScript 會正確推論陣列方法的回傳型別:

let numbers: number[] = [1, 2, 3, 4, 5];

// map 回傳新陣列,元素型別根據回呼函式決定
let doubled: number[] = numbers.map((n) => n * 2);
let strings: string[] = numbers.map((n) => n.toString());

// filter 回傳相同型別的陣列
let evens: number[] = numbers.filter((n) => n % 2 === 0);

// find 可能回傳 undefined
let found: number | undefined = numbers.find((n) => n > 3);

// reduce 回傳累加器的型別
let sum: number = numbers.reduce((acc, n) => acc + n, 0);

多維陣列

// 二維陣列
let matrix: number[][] = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
];

// 三維陣列
let cube: number[][][] = [
  [
    [1, 2],
    [3, 4],
  ],
  [
    [5, 6],
    [7, 8],
  ],
];

元組 (Tuple)

元組是固定長度、每個位置有特定型別的陣列。

基本用法

// 定義一個元組:第一個元素是 string,第二個是 number
let person: [string, number] = ['小明', 25];

// 存取元素
let name: string = person[0];
let age: number = person[1];

// 錯誤的用法
// let wrong: [string, number] = [25, "小明"];  // 順序錯誤
// let wrong2: [string, number] = ["小明"];     // 缺少元素
// let wrong3: [string, number] = ["小明", 25, true];  // 多餘元素

元組的解構

let point: [number, number] = [10, 20];
let [x, y] = point;
console.log(x, y); // 10 20

let rgb: [number, number, number] = [255, 128, 0];
let [red, green, blue] = rgb;

可選元素

使用 ? 標記可選元素:

// 第三個元素是可選的
let coordinate: [number, number, number?] = [10, 20];
let coordinate3D: [number, number, number?] = [10, 20, 30];

// 存取可選元素時可能是 undefined
let z: number | undefined = coordinate[2];

剩餘元素 (Rest Elements)

元組可以包含剩餘元素:

// 前兩個元素固定,後面可以有任意數量的 number
let tuple: [string, number, ...boolean[]] = ['hello', 42, true, false, true];

// 函式參數的應用
function log(message: string, ...codes: number[]): void {
  console.log(message, codes);
}

唯讀元組

let readonly_tuple: readonly [string, number] = ['小明', 25];
// readonly_tuple[0] = "小華";  // 錯誤:無法指派給 readonly 屬性

// 使用 as const 建立唯讀元組
let point = [10, 20] as const;
// point 的型別是 readonly [10, 20]

具名元組 (Labeled Tuples)

TypeScript 4.0+ 支援為元組元素命名,提高可讀性:

type Point = [x: number, y: number];
type RGB = [red: number, green: number, blue: number];

let point: Point = [10, 20];
let color: RGB = [255, 128, 0];

// 函式參數
function setCoordinate(...args: [x: number, y: number, z?: number]) {
  const [x, y, z] = args;
  console.log(x, y, z);
}

陣列 vs 元組

特性陣列 (Array)元組 (Tuple)
長度可變固定
元素型別相同可以不同
用途同質資料集合固定結構的異質資料
範例[1, 2, 3, 4, 5]["小明", 25, true]

實際應用場景

元組作為函式回傳值

// 回傳多個值
function getNameAndAge(): [string, number] {
  return ['小明', 25];
}

const [name, age] = getNameAndAge();

// React 的 useState Hook 就是回傳元組
function useState<T>(initial: T): [T, (value: T) => void] {
  let state = initial;
  const setState = (value: T) => {
    state = value;
  };
  return [state, setState];
}

const [count, setCount] = useState(0);

座標與顏色

type Coordinate = [number, number];
type RGB = [number, number, number];
type RGBA = [number, number, number, number];

const origin: Coordinate = [0, 0];
const red: RGB = [255, 0, 0];
const transparentBlue: RGBA = [0, 0, 255, 0.5];

鍵值對

type Entry = [string, number];

const entries: Entry[] = [
  ['apple', 3],
  ['banana', 5],
  ['orange', 2],
];

// 轉換為 Map
const map = new Map(entries);

常見錯誤

超出索引存取

let tuple: [string, number] = ['hello', 42];

// TypeScript 4.1+ 會對越界存取報錯
// let third = tuple[2];  // 錯誤:索引 2 不存在於型別 [string, number]

混淆陣列和元組

// 這是陣列,元素可以是 string 或 number
let arr: (string | number)[] = ['hello', 42, 'world', 100];

// 這是元組,嚴格限制位置和型別
let tuple: [string, number] = ['hello', 42];