Appearance
TypeScript 元组
在 TypeScript 中,元组(Tuple)是一种特殊的数组类型,它允许存储不同类型的元素,并且长度是固定的。元组是 TypeScript 特有的类型,它提供了一种类型安全的方式来表示具有固定结构的数据。本文将详细介绍 TypeScript 中的元组类型。
1. 基本用法
声明元组
元组的声明使用方括号 [],并在其中指定每个元素的类型。
typescript
// 声明一个包含字符串和数字的元组
let person: [string, number] = ["John", 30];
// 声明一个包含多个类型的元组
let employee: [number, string, boolean] = [1, "John", true];
// 声明一个空元组
let empty: [] = [];访问元组元素
元组的元素可以通过索引访问,与数组类似。
typescript
let person: [string, number] = ["John", 30];
console.log(person[0]); // 输出:John
console.log(person[1]); // 输出:30修改元组元素
元组的元素可以通过索引修改,但是类型必须与声明时的类型一致。
typescript
let person: [string, number] = ["John", 30];
person[0] = "Jane"; // 正确:类型匹配
person[1] = 25; // 正确:类型匹配
// 错误:类型不匹配
// person[0] = 123; // 类型 'number' 不能赋值给类型 'string'
// person[1] = "30"; // 类型 'string' 不能赋值给类型 'number'元组的长度
元组的长度是固定的,不能添加或删除元素。
typescript
let person: [string, number] = ["John", 30];
console.log(person.length); // 输出:2
// 错误:元组长度固定,不能添加元素
// person.push("Engineer"); // 类型 '[string, number]' 上不存在属性 'push'
// 错误:元组长度固定,不能删除元素
// person.pop(); // 类型 '[string, number]' 上不存在属性 'pop'2. 类型注解
基本类型元组
typescript
// 字符串和数字的元组
let person: [string, number] = ["John", 30];
// 布尔值和字符串的元组
let flag: [boolean, string] = [true, "active"];
// 数字和数字的元组
let coordinates: [number, number] = [10, 20];复杂类型元组
typescript
// 对象和数组的元组
let data: [object, number[]] = [{ name: "John" }, [1, 2, 3]];
// 函数和字符串的元组
let funcTuple: [() => void, string] = [() => console.log("hello"), "message"];
// 接口和联合类型的元组
interface User { id: number; name: string; }
let userTuple: [User, string | number] = [{ id: 1, name: "John" }, "admin"];可选元素
元组可以包含可选元素,使用 ? 标记。
typescript
// 可选元素的元组
let person: [string, number, string?] = ["John", 30];
let personWithTitle: [string, number, string?] = ["John", 30, "Engineer"];
// 多个可选元素
let data: [string, number?, boolean?] = ["test"];
let dataWithNumber: [string, number?, boolean?] = ["test", 123];
let dataWithAll: [string, number?, boolean?] = ["test", 123, true];剩余元素
元组可以包含剩余元素,使用 ... 标记。
typescript
// 剩余元素的元组
let numbers: [number, ...number[]] = [1, 2, 3, 4, 5];
let mixed: [string, ...number[]] = ["test", 1, 2, 3];
let anyRest: [string, ...any[]] = ["test", 1, true, { name: "John" }];泛型元组
typescript
// 泛型元组
function createPair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
let pair1 = createPair("hello", 123); // 类型为 [string, number]
let pair2 = createPair(true, { name: "John" }); // 类型为 [boolean, { name: string }]3. 元组操作
解构赋值
元组可以使用解构赋值来提取元素。
typescript
let person: [string, number] = ["John", 30];
// 解构赋值
let [name, age] = person;
console.log(name); // 输出:John
console.log(age); // 输出:30
// 解构部分元素
let [nameOnly] = person;
console.log(nameOnly); // 输出:John
// 解构剩余元素
let numbers: [number, ...number[]] = [1, 2, 3, 4, 5];
let [first, ...rest] = numbers;
console.log(first); // 输出:1
console.log(rest); // 输出:[2, 3, 4, 5]
// 解构可选元素
let personWithTitle: [string, number, string?] = ["John", 30, "Engineer"];
let [name2, age2, title] = personWithTitle;
console.log(title); // 输出:Engineer
let personWithoutTitle: [string, number, string?] = ["John", 30];
let [name3, age3, title2] = personWithoutTitle;
console.log(title2); // 输出:undefined展开操作符
元组可以使用展开操作符来合并元组。
typescript
let person: [string, number] = ["John", 30];
let details: [string, boolean] = ["Engineer", true];
// 合并元组
let combined: [string, number, string, boolean] = [...person, ...details];
console.log(combined); // 输出:["John", 30, "Engineer", true]
// 展开到数组
let array: string[] = [...person];
console.log(array); // 输出:["John", "30"]元组与数组的转换
元组可以转换为数组,数组也可以转换为元组(需要类型断言)。
typescript
// 元组转换为数组
let person: [string, number] = ["John", 30];
let array: string[] = [...person];
let array2: (string | number)[] = person;
// 数组转换为元组(需要类型断言)
let array3: (string | number)[] = ["John", 30];
let person2: [string, number] = array3 as [string, number];4. 类型守卫
在 TypeScript 中,可以使用类型守卫来检查一个值是否是元组类型。
自定义类型守卫
typescript
// 检查是否是元组
function isTuple(value: any): value is any[] {
return Array.isArray(value) && value.length > 0;
}
// 检查是否是特定类型的元组
function isStringNumberTuple(value: any): value is [string, number] {
return Array.isArray(value) && value.length === 2 && typeof value[0] === "string" && typeof value[1] === "number";
}
function processValue(value: any) {
if (isStringNumberTuple(value)) {
// 在这个分支中,TypeScript 知道 value 是 [string, number] 类型
console.log(`Name: ${value[0]}, Age: ${value[1]}`);
} else {
console.log(`Not a [string, number] tuple: ${value}`);
}
}
processValue(["John", 30]); // 输出:Name: John, Age: 30
processValue(["John"]); // 输出:Not a [string, number] tuple: John
processValue([1, 2]); // 输出:Not a [string, number] tuple: 1,25. 元组的最佳实践
1. 使用类型注解
为元组添加类型注解可以提高代码的可读性和类型安全性。
typescript
// 推荐
let person: [string, number] = ["John", 30];
// 不推荐
let person = ["John", 30]; // 类型推断为 (string | number)[],失去了元组的类型安全2. 使用 const 声明只读元组
对于不需要修改的元组,使用 const 声明可以防止意外修改。
typescript
// 推荐
const person: [string, number] = ["John", 30];
// 不推荐
let person: [string, number] = ["John", 30]; // 可能被意外修改3. 合理使用可选元素
当元组的某些元素可能不存在时,使用可选元素可以提高代码的灵活性。
typescript
// 推荐:使用可选元素
let person: [string, number, string?] = ["John", 30];
let personWithTitle: [string, number, string?] = ["John", 30, "Engineer"];
// 不推荐:使用联合类型
let person: [string, number, string | undefined] = ["John", 30, undefined];4. 合理使用剩余元素
当元组的元素数量不确定时,使用剩余元素可以提高代码的灵活性。
typescript
// 推荐:使用剩余元素
let numbers: [number, ...number[]] = [1, 2, 3, 4, 5];
// 不推荐:使用固定长度
let numbers: [number, number, number, number, number] = [1, 2, 3, 4, 5];5. 使用解构赋值提高可读性
对于元组的元素访问,使用解构赋值可以提高代码的可读性。
typescript
// 推荐:使用解构赋值
let person: [string, number] = ["John", 30];
let [name, age] = person;
console.log(`Name: ${name}, Age: ${age}`);
// 不推荐:直接访问索引
let person: [string, number] = ["John", 30];
console.log(`Name: ${person[0]}, Age: ${person[1]}`);6. 注意元组的长度
元组的长度是固定的,不能添加或删除元素,使用时需要注意。
typescript
// 注意:元组长度固定
let person: [string, number] = ["John", 30];
// 错误:不能添加元素
// person.push("Engineer");
// 错误:不能删除元素
// person.pop();6. 常见错误
1. 类型不匹配
在 TypeScript 中,元组的元素类型必须与声明时的类型一致。
typescript
// 错误:类型不匹配
let person: [string, number] = ["John", "30"]; // 类型 'string' 不能赋值给类型 'number'
// 正确
let person: [string, number] = ["John", 30];2. 长度不匹配
元组的长度是固定的,不能多也不能少。
typescript
// 错误:长度不匹配
let person: [string, number] = ["John"]; // 类型 '[string]' 不能赋值给类型 '[string, number]'
// 错误:长度不匹配
let person: [string, number] = ["John", 30, "Engineer"]; // 类型 '[string, number, string]' 不能赋值给类型 '[string, number]'
// 正确
let person: [string, number] = ["John", 30];3. 访问越界索引
访问元组中不存在的索引会导致类型错误。
typescript
let person: [string, number] = ["John", 30];
// 错误:访问越界索引
// console.log(person[2]); // 长度为 "2" 的元组类型 '[string, number]' 在索引 "2" 处没有元素
// 正确
console.log(person[0]); // 输出:John
console.log(person[1]); // 输出:304. 尝试修改元组长度
元组的长度是固定的,不能通过修改 length 属性来改变长度。
typescript
let person: [string, number] = ["John", 30];
// 错误:不能修改元组长度
// person.length = 3; // 无法分配到 "length" ,因为它是只读属性
// 正确
console.log(person.length); // 输出:25. 混淆元组和数组
元组和数组是不同的类型,混淆它们会导致错误。
typescript
// 错误:混淆元组和数组
let person: [string, number] = ["John", 30];
let array: string[] = person; // 类型 '[string, number]' 不能赋值给类型 'string[]'
// 正确:使用类型断言
let person: [string, number] = ["John", 30];
let array: (string | number)[] = person;7. 元组与数组的比较
1. 长度
- 元组:长度固定,不能添加或删除元素。
- 数组:长度可变,可以添加或删除元素。
2. 类型
- 元组:每个元素的类型可以不同,并且类型是固定的。
- 数组:所有元素的类型必须相同(或使用联合类型)。
3. 类型安全
- 元组:提供更强的类型安全,访问元素时会检查类型。
- 数组:类型安全相对较弱,访问元素时需要手动检查类型。
4. 用途
- 元组:适用于固定结构的数据,如坐标、颜色值、键值对等。
- 数组:适用于可变长度的同类型数据集合。
8. 实际应用场景
1. 坐标表示
typescript
// 二维坐标
let point: [number, number] = [10, 20];
// 三维坐标
let point3D: [number, number, number] = [10, 20, 30];
// 操作坐标
function distance(p1: [number, number], p2: [number, number]): number {
return Math.sqrt(Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2));
}
let p1: [number, number] = [0, 0];
let p2: [number, number] = [3, 4];
console.log(distance(p1, p2)); // 输出:52. 颜色表示
typescript
// RGB 颜色
let rgb: [number, number, number] = [255, 0, 0]; // 红色
// RGBA 颜色
let rgba: [number, number, number, number] = [255, 0, 0, 1]; // 不透明红色
// 操作颜色
function toHexColor(rgb: [number, number, number]): string {
return `#${rgb.map(c => c.toString(16).padStart(2, '0')).join('')}`;
}
let red: [number, number, number] = [255, 0, 0];
console.log(toHexColor(red)); // 输出:#ff00003. 函数返回多个值
typescript
// 返回多个值
function getUser(): [string, number, boolean] {
return ["John", 30, true];
}
// 使用解构赋值
let [name, age, active] = getUser();
console.log(`Name: ${name}, Age: ${age}, Active: ${active}`); // 输出:Name: John, Age: 30, Active: true4. 键值对
typescript
// 键值对元组
let keyValue: [string, any][] = [
["name", "John"],
["age", 30],
["active", true]
];
// 转换为对象
function toObject(pairs: [string, any][]): Record<string, any> {
return pairs.reduce((obj, [key, value]) => {
obj[key] = value;
return obj;
}, {});
}
let user = toObject(keyValue);
console.log(user); // 输出:{ name: "John", age: 30, active: true }5. 函数参数
typescript
// 函数参数为元组
function printPerson(person: [string, number]): void {
console.log(`Name: ${person[0]}, Age: ${person[1]}`);
}
printPerson(["John", 30]); // 输出:Name: John, Age: 30
// 使用剩余参数
function sum(first: number, ...rest: number[]): number {
return first + rest.reduce((acc, num) => acc + num, 0);
}
sum(1, 2, 3, 4, 5); // 输出:15总结
TypeScript 中的元组是一种特殊的数组类型,它允许存储不同类型的元素,并且长度是固定的。它包括:
- 基本用法:声明元组,访问和修改元组元素,元组的长度
- 类型注解:基本类型元组,复杂类型元组,可选元素,剩余元素,泛型元组
- 元组操作:解构赋值,展开操作符,元组与数组的转换
- 类型守卫:使用自定义类型守卫检查元组类型
- 最佳实践:使用类型注解,使用 const 声明只读元组,合理使用可选元素和剩余元素,使用解构赋值提高可读性,注意元组的长度
- 常见错误:类型不匹配,长度不匹配,访问越界索引,尝试修改元组长度,混淆元组和数组
- 元组与数组的比较:长度,类型,类型安全,用途
- 实际应用场景:坐标表示,颜色表示,函数返回多个值,键值对,函数参数
通过合理使用元组,你可以在 TypeScript 中更安全、更清晰地表示具有固定结构的数据,提高代码的可读性和类型安全性。