Skip to content

TypeScript - 元组

元组 (Tuples) 是 TypeScript 中的一种特殊类型,表示固定大小的数组,其中每个特定位置(索引 index)上元素的类型是已知且固定的。与通常包含同类型元素且可以增长或收缩的普通数组不同,元组为少量相关值的集合定义了一个精确的结构。

可以把元组看作是为一个特定长度的数组中的每个元素定义类型。

元组类型使用方括号 [] 声明,其中按顺序列出各个元素的类型。

let tupleName: [type1, type2, type3, ..., typeN];
// 一个表示键值对的元组(string 键,number 值)
let keyValue: [string, number];
keyValue = ["userId", 12345]; // 正确
// 一个表示 RGB 颜色值的元组
let rgbColor: [number, number, number];
rgbColor = [255, 0, 128]; // 正确
// 一个混合类型的元组
let personRecord: [string, number, boolean];
personRecord = ["Alice", 30, true]; // 正确
// 不正确的赋值(类型或顺序错误)
// keyValue = [12345, "userId"]; // 错误: 类型 'number' 不能赋值给类型 'string'。类型 'string' 不能赋值给类型 'number'。
// rgbColor = [255, 0]; // 错误: 类型 '[number, number]' 不能赋值给类型 '[number, number, number]'。源有 2 个元素,目标需要 3 个。
// personRecord = ["Bob", 40]; // 错误: 类型 '[string, number]' 不能赋值给类型 '[string, number, boolean]'。

元组元素像数组一样,使用从零开始的数字索引 (indices) 进行访问。TypeScript 为每个索引提供了类型安全性。

let employee: [number, string, string] = [101, "John Doe", "Engineering"];
let employeeId: number = employee[0]; // 已知类型为 number
let employeeName: string = employee[1]; // 已知类型为 string
let department: string = employee[2]; // 已知类型为 string
// let manager: string = employee[3]; // 错误: 长度为 '3' 的元组类型 '[number, string, string]' 在索引 '3' 处没有元素。
console.log(`ID: ${employeeId}`);
console.log(`Name: ${employeeName}`);
console.log(`Department: ${department}`);
// 修改元素(遵守该索引处的类型)
employee[2] = "Marketing";
// employee[0] = "E101"; // 错误: 类型 'string' 不能赋值给类型 'number'。

虽然元组在编译时定义了固定结构,但标准 JavaScript 数组方法如 push、pop 等,在运行时理论上仍然可以在元组上调用。然而,通常应避免这样做,因为它破坏了元组预期的固定大小、固定索引类型的特性,并且 TypeScript 不一定能捕获超出定义长度的修改所导致的类型错误。

最佳实践是创建后将元组视为固定长度的结构。

let myTuple: [string, number] = ["Count", 10];
// 允许更新元素并进行类型检查:
myTuple[1] = 20;
console.log(myTuple); // 输出: [ 'Count', 20 ]
// 使用 push 可能会绕过编译时的长度检查(谨慎使用!):
// myTuple.push(true); // TS 4.0+ 中的错误:类型 boolean 的参数不能赋值给 string | number
// 在旧版 TS 或类型检查不那么严格的情况下,这可能编译通过,
// 但 `myTuple` 不再严格符合 [string, number] 的形状。
// console.log(myTuple);
// pop() 也起作用,但同样会修改元组的结构
// let removed = myTuple.pop();
// console.log(removed);
// console.log(myTuple);

为了实现不可变性 (immutability),可以考虑使用 readonly 元组。

使用 readonly 关键字使元组不可变。

const readOnlyCoord: readonly [number, number] = [10, 5];
// readOnlyCoord[0] = 15; // 错误: 类型 'readonly [number, number]' 中的索引签名仅允许读取。
// readOnlyCoord.push(20); // 错误: 类型 'readonly [number, number]' 上不存在属性 'push'。

与数组解构类似,可以轻松地将元组元素解包到单独的变量中。

const httpStatus: [number, string] = [200, "OK"];
const [statusCode, statusMessage] = httpStatus;
console.log(`Status Code: ${statusCode}`); // 输出: Status Code: 200
console.log(`Status Message: ${statusMessage}`); // 输出: Status Message: OK
// 解构时应用类型检查
// const [code, message, description]: [number, string, string] = httpStatus; // 错误: 长度为 '2' 的元组类型 '[number, string]' 在索引 '2' 处没有元素。

可选元组元素 (Optional Tuple Elements)

Section titled “可选元组元素 (Optional Tuple Elements)”

可以使用 ? 将元素标记为可选。可选元素必须出现在元组的末尾。

let optionalTuple: [string, number, boolean?];
optionalTuple = ["User", 1, true]; // 正确
optionalTuple = ["Admin", 2]; // 正确(省略了可选元素)
console.log(optionalTuple[0]); // string
console.log(optionalTuple[1]); // number
console.log(optionalTuple[2]); // boolean | undefined

元组末尾可以有一个剩余元素 (...Type[]),用于表示任意数量的特定类型的尾随元素。

// 必须至少包含一个 string 和一个 number,后跟任意数量的 boolean
let mixedRestTuple: [string, number, ...boolean[]];
mixedRestTuple = ["Data", 100]; // 正确
mixedRestTuple = ["Data", 100, true]; // 正确
mixedRestTuple = ["Data", 100, true, false, true]; // 正确
console.log(mixedRestTuple[0]); // string
console.log(mixedRestTuple[1]); // number
console.log(mixedRestTuple.slice(2)); // boolean[]

当你需要从函数返回多个值,或者表示一个小型、固定的集合,并且其中每个元素的位置都有特定含义和类型时,元组非常有用。