首页 / TypeScript 入门教程 / 元组(Tuple)

TypeScript 入门教程

元组(Tuple)

本教程共 80 篇 · 第 11 篇 · 更新于 2026-08-10 · 约 10 分钟阅读

TypeScriptTypeScript 入门教程元组Tuple标签元组

本节目标:理解元组和数组的区别,掌握元组的定长定序特性,学会使用可选元素、剩余元素和标签元组,知道什么时候用元组、什么时候用对象更好。

元组是什么

数组要求所有元素类型相同。但有时候你有一组”结构固定、类型各不相同”的数据——比如一个函数要同时返回数值和状态信息,或者一个表格行包含姓名和年龄。这时候数组不够精确,你需要元组

// 数组:元素类型统一,长度可变
let scores: number[] = [95, 88, 76];

// 元组:每个位置有各自的类型,长度固定
let person: [string, number] = ["小明", 18];

元组在定义时就说清楚了:第 0 位是 string,第 1 位是 number。顺序错、类型错、多一个元素都不行:

let person: [string, number];

person = ["小明", 18];       // ✅ 正确
// person = [18, "小明"];    // ❌ 顺序错了
// person = ["小明"];        // ❌ 少了一个元素
// person = ["小明", 18, true];  // ❌ 多了一个元素

基本操作

元组底层就是 JavaScript 数组,所以可以用索引访问、解构等方式读取:

let user: [string, number, boolean] = ["小明", 18, true];

// 索引访问
let name: string = user[0];    // "小明"
let age: number = user[1];     // 18
let isActive: boolean = user[2];  // true

// 解构
let [userName, userAge, active] = user;
console.log(userName, userAge, active);  // "小明" 18 true

// forEach 遍历
user.forEach((item) => console.log(item));
Note

虽然元组在类型层面有固定长度,运行时它仍然是数组。这意味着 pushpop 等方法依然可用——但 在 strict 模式下,TypeScript 会阻止向元组推入不兼容类型的元素。

可选元素

有时候元组的某个位置可填可不填。用 ? 把对应位置标为可选:

// 第三个元素可选
let config: [string, number, boolean?];

config = ["host", 8080];          // ✅ 两个也行
config = ["host", 8080, true];    // ✅ 三个也行
// config = ["host", 8080, "extra"];  // ❌ 第三个位置只能是 boolean

// 多个可选元素
let info: [string, number?, boolean?];
info = ["小明"];                   // ✅
info = ["小明", 18];              // ✅
info = ["小明", 18, false];       // ✅

可选元素必须放在必选元素后面——你不能把可选元素夹在中间。

剩余元素

剩余元素语法让元组的”尾巴”可以装任意多个同类型的值:

// 第一个是 string,后面可以跟任意多个 number
let data: [string, ...number[]];

data = ["scores"];              // ✅ 没有数字
data = ["scores", 95];          // ✅ 一个数字
data = ["scores", 95, 88, 76];  // ✅ 多个数字

这实际上是把元组和数组组合在一起——“头部固定、尾部自由”。真实场景中很好用,比如函数参数列表:

// 第一个参数是事件名,剩下的是事件数据
type Event = [name: string, ...data: any[]];

let clickEvent: Event = ["click", { x: 100, y: 200 }];
let keyEvent: Event = ["keydown", "Enter", true];

标签元组

标签元组(labeled tuple)给每个位置起个名字,让人一眼看懂每个元素的含义:

// 没有标签:看不出每个位置代表什么
let person1: [string, number] = ["小明", 18];

// 有标签:语义一目了然
let person2: [name: string, age: number] = ["小明", 18];

// 访问时标签纯粹是文档作用,不影响运行时
console.log(person2[0]);  // "小明"

标签只存在于类型层面,编译后消失。它们不影响索引访问和解构,但编辑器的智能提示会显示这些标签,大幅提升代码可读性。

// React 用户应该很熟悉——useState 返回的就是标签元组
function useState<T>(initial: T): [value: T, setter: (v: T) => void] {
  // 内部实现...
  return [initial, (v) => { /* ... */ }];
}

const [count, setCount] = useState(0);
// count 的类型是 number,setCount 的类型是 (v: number) => void

只读元组

和数组一样,元组也可以标记为 readonly,禁止修改:

let point: readonly [number, number] = [10, 20];

// point[0] = 30;        // ❌ 只读,不能修改
// point.push(30);       // ❌ 没有 push 方法
console.log(point[0]);   // ✅ 读取没问题
Tip

任何你不期望被修改的元组,都建议加上 readonly。尤其是作为函数返回值时,只读元组能防止调用方意外修改数据。

元组 vs 对象:什么时候用哪个

元组和对象都能表示多字段数据,选哪个要看场景:

// 元组:适合"顺序有意义"的场景
let httpResponse: [number, string] = [200, "OK"];

// 对象:适合"字段有意义"的场景
let httpResponse2 = {
  status: 200,
  message: "OK"
};

用元组:数据很短(2-3 个字段),顺序天然代表含义(坐标、键值对、函数多返回值)。

用对象:字段超过 3 个,或者每个字段的含义不能靠位置暗示——给别人看的代码,response.status 远比 response[0] 好懂。

一个折中的方案是用标签元组——既有位置索引的简洁,又有标签的可读性。

类型推断中的”坑”

不加类型注解时,TypeScript 会倾向于把数组字面量推断为普通数组而非元组:

// 不加注解 → 推断为 (string | number)[]
let pair = ["小明", 18];     // 类型: (string | number)[]

// 加了注解 → 推断为元组
let pair2: [string, number] = ["小明", 18];  // 类型: [string, number]

如果你想要元组又不写注解,用 as const

let pair3 = ["小明", 18] as const;
// 类型: readonly ["小明", 18]
// 既固定了长度和类型,又加了 readonly

可运行示例

// 元组综合示例

// 1. 基本元组:坐标点
type Point2D = [x: number, y: number];
let origin: Point2D = [0, 0];
let target: Point2D = [100, 200];

function distance(from: Point2D, to: Point2D): number {
  let dx = to[0] - from[0];
  let dy = to[1] - from[1];
  return Math.sqrt(dx * dx + dy * dy);
}

console.log(`两点距离: ${distance(origin, target).toFixed(2)}`);

// 2. 可选元素:用户信息(邮箱可选)
type UserRecord = [name: string, age: number, email?: string];

let users: UserRecord[] = [
  ["小明", 18],
  ["小红", 20, "xiaohong@example.com"],
  ["小刚", 22]
];

users.forEach(([name, age, email]) => {
  let info = `${name} (${age}岁)`;
  if (email) info += ` - ${email}`;
  console.log(info);
});

// 3. 剩余元素:成绩单
type ScoreReport = [subject: string, ...scores: number[]];

let mathReport: ScoreReport = ["数学", 95, 88, 92, 76];
let total = mathReport.slice(1).reduce((sum, s) => sum + s, 0);
let avg = total / (mathReport.length - 1);
console.log(`${mathReport[0]} 平均分: ${avg.toFixed(1)}`);

// 4. 只读元组
const RGB_WHITE: readonly [number, number, number] = [255, 255, 255];
console.log("白色 RGB:", RGB_WHITE);

输出:

两点距离: 223.61
小明 (18岁)
小红 (20岁) - xiaohong@example.com
小刚 (22岁)
数学 平均分: 87.8
白色 RGB: [ 255, 255, 255 ]

小结

元组填补了数组”所有元素同类型”的限制,让你能用类型精确描述定长定序的数据结构。标签元组和只读元组让可读性和安全性更上一层。元组虽好用,但别滥用——超过 3 个元素时考虑换成对象。下一章看枚举,TypeScript 里争议最大的特性之一。