元组类型

元组是一种固定长度、元素类型确定的数组。与普通数组不同,元组中每个位置的元素类型是固定的。

基本用法

let tuple: [string, number] = ["张三", 25]

console.log(tuple[0])  // "张三" - string 类型
console.log(tuple[1])  // 25 - number 类型

元素类型固定

元组中每个位置的元素类型是确定的:

let user: [string, number, boolean] = ["张三", 25, true]

let name: string = user[0]     // 正确
let age: number = user[1]      // 正确
let active: boolean = user[2]  // 正确

let wrong: string = user[1]    // 错误:类型不匹配

访问越界

访问已知索引是安全的:

let tuple: [string, number] = ["hello", 10]

console.log(tuple[0].toUpperCase())  // "HELLO" - string 方法
console.log(tuple[1].toFixed(2))     // "10.00" - number 方法

可选元素

let tuple: [string, number?] = ["张三"]

console.log(tuple.length)  // 1

tuple[1] = 25
console.log(tuple.length)  // 2

剩余元素

let tuple: [string, ...number[]] = ["张三", 1, 2, 3, 4, 5]

console.log(tuple[0])      // "张三"
console.log(tuple.slice(1)) // [1, 2, 3, 4, 5]

只读元组

let readonlyTuple: readonly [string, number] = ["张三", 25]

readonlyTuple[0] = "李四"  // 错误:只读
readonlyTuple.push(30)     // 错误:只读

实际应用

函数返回多个值

function getUserInfo(): [string, number, boolean] {
  return ["张三", 25, true]
}

const [name, age, isActive] = getUserInfo()
console.log(name, age, isActive)  // 张三 25 true

键值对

let entries: [string, number][] = [
  ["apple", 5],
  ["banana", 3],
  ["orange", 4]
]

let map = new Map(entries)
console.log(map.get("apple"))  // 5

坐标点

type Point = [number, number]
type Point3D = [number, number, number]

let point2D: Point = [10, 20]
let point3D: Point3D = [10, 20, 30]

function distance(a: Point, b: Point): number {
  return Math.sqrt(
    Math.pow(b[0] - a[0], 2) + Math.pow(b[1] - a[1], 2)
  )
}

console.log(distance([0, 0], [3, 4]))  // 5

RGB 颜色

type RGB = [number, number, number]

function toHex(rgb: RGB): string {
  return "#" + rgb
    .map(n => n.toString(16).padStart(2, "0"))
    .join("")
}

console.log(toHex([255, 128, 0]))  // #ff8000

表格行数据

type TableRow = [number, string, number, string]

const rows: TableRow[] = [
  [1, "苹果", 5, "水果"],
  [2, "白菜", 3, "蔬菜"],
  [3, "牛肉", 50, "肉类"]
]

function renderTable(rows: TableRow[]): string {
  return rows.map(row => row.join(" | ")).join("\n")
}

console.log(renderTable(rows))
// 1 | 苹果 | 5 | 水果
// 2 | 白菜 | 3 | 蔬菜
// 3 | 牛肉 | 50 | 肉类

解构赋值

let user: [string, number] = ["张三", 25]

let [name, age] = user
console.log(name, age)  // 张三 25

let [first, ...rest]: [number, number, number, number] = [1, 2, 3, 4]
console.log(first)  // 1
console.log(rest)   // [2, 3, 4]

元组方法

元组本质上是数组,可以使用数组方法:

let tuple: [string, number] = ["张三", 25]

console.log(tuple.length)  // 2
console.log(tuple.concat(["李四", 30]))  // ["张三", 25, "李四", 30]

tuple.push("extra")  // 可以添加,但不推荐
console.log(tuple)   // ["张三", 25, "extra"]

元组 vs 数组

特性元组数组
长度固定可变
元素类型每个位置确定所有元素相同
访问索引类型确定所有元素类型相同
let array: number[] = [1, 2, 3]
array.push(4)  // 正确

let tuple: [number, number] = [1, 2]
tuple.push(3)  // 可以,但不推荐

注意事项

长度限制

let tuple: [string, number] = ["张三", 25]

tuple = ["李四"]  // 错误:缺少元素
tuple = ["李四", 30, true]  // 错误:元素过多

类型安全

let tuple: [string, number] = ["张三", 25]

tuple[0] = 123  // 错误:类型不匹配
tuple[1] = "30"  // 错误:类型不匹配

越界访问

let tuple: [string, number] = ["张三", 25]

let third = tuple[2]  // 类型为 undefined,但不会报错

标签元组

TypeScript 4.0+ 支持元组标签:

type UserTuple = [name: string, age: number, active: boolean]

function createUser(tuple: UserTuple): void {
  const [name, age, active] = tuple
  console.log(name, age, active)
}

createUser(["张三", 25, true])