数组和元组(Array and Tuple)
概述
TypeScript 提供了丰富的数组和元组类型定义方式,用于精确描述数据结构。
数组类型
基本语法
// 类型 + 方括号
let numbers: number[] = [1, 2, 3, 4, 5];
let strings: string[] = ['hello', 'world'];
let booleans: boolean[] = [true, false];
// 泛型语法
let numbers2: Array<number> = [1, 2, 3];
let strings2: Array<string> = ['hello', 'world'];
// 类型推断
let inferred = [1, 2, 3]; // 推断为 number[]
let mixed = [1, 'two', true]; // 推断为 (string | number | boolean)[]
只读数组
// readonly 关键字
let readonlyNumbers: readonly number[] = [1, 2, 3];
// readonlyNumbers.push(4); // 错误
// readonlyNumbers[0] = 10; // 错误
// ReadonlyArray 泛型
let readonlyStrings: ReadonlyArray<string> = ['a', 'b', 'c'];
// as const
const constArray = [1, 2, 3] as const;
// constArray.push(4); // 错误
多维数组
// 二维数组
let matrix: number[][] = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
// 三维数组
let cube: number[][][] = [
[[1, 2], [3, 4]],
[[5, 6], [7, 8]]
];
// 泛型写法
let matrix2: Array<Array<number>> = [[1, 2], [3, 4]];
数组方法类型
// 数组方法返回类型
const numbers: number[] = [1, 2, 3, 4, 5];
// map 返回新类型
const strings: string[] = numbers.map(n => n.toString());
// filter 类型守卫
const items: (string | number)[] = [1, 'two', 3, 'four'];
const numbersOnly: number[] = items.filter((item): item is number =>
typeof item === 'number'
);
// reduce 指定类型
const sum: number = numbers.reduce((acc, n) => acc + n, 0);
const sum2: number = numbers.reduce<number>((acc, n) => acc + n, 0);
元组类型
基本语法
// 元组:固定长度、固定类型的数组
let tuple: [string, number] = ['John', 25];
// 访问元素
let name: string = tuple[0]; // 'John'
let age: number = tuple[1]; // 25
// 类型顺序必须匹配
// let wrong: [string, number] = [25, 'John']; // 错误
// 长度必须匹配
// let wrong2: [string, number] = ['John']; // 错误
// let wrong3: [string, number] = ['John', 25, true]; // 错误
可选元素
// 可选元素(必须放在最后)
let tuple1: [string, number, boolean?] = ['John', 25];
let tuple2: [string, number, boolean?] = ['John', 25, true];
// 多个可选元素
let tuple3: [string, number?, boolean?] = ['John'];
let tuple4: [string, number?, boolean?] = ['John', 25];
let tuple5: [string, number?, boolean?] = ['John', 25, true];
剩余元素
// 剩余元素
let restTuple: [string, ...number[]] = ['John', 1, 2, 3, 4, 5];
// 剩余元素在中间
let mixed: [string, ...number[], boolean] = ['John', 1, 2, 3, true];
// 复杂剩余元素
let complex: [string, ...Array<[number, boolean]>] = ['John', [1, true], [2, false]];
只读元组
// readonly 元组
let readonlyTuple: readonly [string, number] = ['John', 25];
// readonlyTuple[0] = 'Jane'; // 错误
// as const
const constTuple = ['John', 25] as const;
// constTuple[0] = 'Jane'; // 错误
命名元组
// 命名元组(TypeScript 4.0+)
type User = [name: string, age: number, email: string];
const user: User = ['John', 25, 'john@example.com'];
// 解构时可以使用名称
const [userName, userAge, userEmail] = user;
实际应用示例
1. 函数返回多个值
// 使用元组返回多个值
function divide(a: number, b: number): [number, number] {
const quotient = Math.floor(a / b);
const remainder = a % b;
return [quotient, remainder];
}
const [q, r] = divide(10, 3);
console.log(q, r); // 3, 1
// 命名元组更清晰
type DivisionResult = [quotient: number, remainder: number];
function divide2(a: number, b: number): DivisionResult {
return [Math.floor(a / b), a % b];
}
2. React Hooks
// useState 返回元组
function useState<T>(initial: T): [T, (value: T) => void] {
let state = initial;
const setState = (newValue: T) => {
state = newValue;
};
return [state, setState];
}
const [count, setCount] = useState(0);
const [name, setName] = useState('John');
// 自定义 Hook 返回元组
function useToggle(initial: boolean): [boolean, () => void] {
const [value, setValue] = useState(initial);
const toggle = () => setValue(!value);
return [value, toggle];
}
const [isOpen, toggleOpen] = useToggle(false);
3. 配置选项
// 配置元组
type DatabaseConfig = [host: string, port: number, database: string];
const config: DatabaseConfig = ['localhost', 5432, 'myapp'];
function connect([host, port, database]: DatabaseConfig): void {
console.log(`Connecting to ${host}:${port}/${database}`);
}
// 可选配置
type ApiConfig = [
baseUrl: string,
timeout?: number,
retries?: number
];
const apiConfig1: ApiConfig = ['https://api.example.com'];
const apiConfig2: ApiConfig = ['https://api.example.com', 5000];
const apiConfig3: ApiConfig = ['https://api.example.com', 5000, 3];
4. CSV 数据处理
// CSV 行类型
type CsvRow = [id: number, name: string, email: string, age: number];
const data: CsvRow[] = [
[1, 'John', 'john@example.com', 25],
[2, 'Jane', 'jane@example.com', 30],
[3, 'Bob', 'bob@example.com', 35]
];
// 处理 CSV 数据
function processCsv(rows: CsvRow[]): { name: string; email: string }[] {
return rows.map(([id, name, email, age]) => ({ name, email }));
}
// 过滤数据
function filterByAge(rows: CsvRow[], minAge: number): CsvRow[] {
return rows.filter(([id, name, email, age]) => age >= minAge);
}
5. 坐标和范围
// 2D 坐标
type Point2D = [x: number, y: number];
// 3D 坐标
type Point3D = [x: number, y: number, z: number];
// 范围
type Range = [start: number, end: number];
// 矩形
type Rect = [x: number, y: number, width: number, height: number];
function isInRect(point: Point2D, rect: Rect): boolean {
const [px, py] = point;
const [rx, ry, rw, rh] = rect;
return px >= rx && px <= rx + rw && py >= ry && py <= ry + rh;
}
// 颜色
type RGB = [r: number, g: number, b: number];
type RGBA = [r: number, g: number, b: number, a: number];
6. 缓存和映射
// 键值对元组
type KeyValuePair<K, V> = [key: K, value: V];
const pairs: KeyValuePair<string, number>[] = [
['age', 25],
['score', 100],
['level', 5]
];
// Map 构造
const map = new Map(pairs);
// 缓存条目
type CacheEntry<T> = [key: string, value: T, timestamp: number];
const cache: CacheEntry<User>[] = [
['user:1', { id: 1, name: 'John' }, Date.now()],
['user:2', { id: 2, name: 'Jane' }, Date.now()]
];
最佳实践
- 使用元组返回多个值:比对象更轻量
- 使用命名元组:提高可读性
- 使用只读元组:保护数据不被修改
- 使用剩余元素:处理可变长度数据
// 好的做法
type FetchResult<T> = [data: T | null, error: Error | null];
async function fetchUser(id: number): Promise<FetchResult<User>> {
try {
const user = await getUserById(id);
return [user, null];
} catch (error) {
return [null, error as Error];
}
}
const [user, error] = await fetchUser(1);
// 避免
async function fetchUserBad(id: number): Promise<any> {
// 返回类型不明确
}
参考
目录