第 1 节 概述
前三篇我们学了基础类型和类型运算。现在能描述单个值了。
但真实世界的程序处理的是批量数据——用户列表、时间坐标、键值对……
TypeScript 提供两种结构来处理有序集合:
- 数组——同类型元素的列表
- 元组——固定数量和位置的组合
第 2 节 数组类型
数组有两种写法,意思完全一样:
1
2
| const arr1: string[] = ["a", "b", "c"]
const arr2: Array<string> = ["a", "b", "c"]
|
第一种 T[] 更常用,读起来像"字符串的数组"。第二种 Array<T> 是泛型写法,之后会详讲。
1
2
3
| const numbers: number[] = [1, 2, 3]
const booleans: boolean[] = [true, false]
const mixed: (string | number)[] = ["a", 1, "b", 2]
|
数组的元素没有数量限制:
1
2
3
| const names: string[] = []
const fruits: string[] = ["苹果", "香蕉"]
const many: string[] = new Array(100).fill("x")
|
2.1 readonly 数组
普通数组可以修改元素或长度。如果不希望被修改,可以用 readonly:
1
2
3
4
| const arr: readonly string[] = ["a", "b", "c"]
arr[0] = "x" // ❌ 只读,不能修改
arr.push("d") // ❌ 只读,不能添加
|
只读数组在函数参数中很有用——告诉调用者"我不会改你的数据":
1
2
3
4
| function logItems(items: readonly string[]) {
items.push("d") // ❌ 编译报错,安全
console.log(items)
}
|
ReadonlyArray<T> 和 readonly T[] 等价:
1
2
| const arr1: readonly string[] = ["a", "b"]
const arr2: ReadonlyArray<string> = ["a", "b"]
|
第 3 节 元组
数组的元素类型一致、数量不限。但有些数据天然就是固定结构的——比如经纬度、二维坐标:
1
2
| // 用数组不够精确
const position: number[] = [121.5, 31.2] // 谁知道哪个是经度哪个是纬度?
|
元组(Tuple) 就是解决这个问题的。它限定了元素的数量和类型:
1
2
3
4
5
| type Coordinate = [number, number] // 经度,纬度
type Pair = [string, number] // 名字,年龄
const position: Coordinate = [121.5, 31.2]
const item: Pair = ["张三", 18]
|
元组的位置是有意义的。第一个和第二个的类型可以不同:
1
2
3
4
| type HttpResult = [number, string] // [状态码, 消息]
const success: HttpResult = [200, "OK"]
const notFound: HttpResult = [404, "Not Found"]
|
3.1 可选元素
元组的某些位置可以标记为可选:
1
2
3
4
| type Count = [string, number?]
const c1: Count = ["数量"]
const c2: Count = ["数量", 10]
|
3.2 元组标签
TypeScript 4.0 后,可以为元组的每个位置加标签,说明其含义:
1
2
3
4
5
6
7
8
| type Coordinate = [longitude: number, latitude: number]
function getPosition(): Coordinate {
return [121.5, 31.2]
}
const [lng, lat] = getPosition()
// 悬停查看:lng 显示为 longitude,lat 显示为 latitude
|
标签在开发工具中会显示出来,提高代码可读性。
3.3 元组的实际用途
元组在前端开发中非常常见。
React 的 useState:
1
2
3
4
| // useState 返回的就是元组
const [count, setCount] = useState(0)
// const [state, setState] = useState<number>(initialValue)
// 类型是 [number, Dispatch<SetStateAction<number>>]
|
解构赋值的类型安全:
1
2
3
4
5
6
7
| function splitName(fullName: string): [string, string] {
const parts = fullName.split(" ")
return [parts[0], parts[1] || ""]
}
const [first, last] = splitName("张三")
// first 的类型是 string,last 的类型也是 string
|
变长元组(Rest 元素):
1
2
3
4
5
6
7
| type StringWithNumbers = [string, ...number[]]
const a: StringWithNumbers = ["hello", 1, 2, 3]
const b: StringWithNumbers = ["hello"]
type HeadAndTail = [string, ...boolean[]]
// 第一个是 string,后面全是 boolean
|
第 4 节 数组 vs 元组
| 对比 | 数组 | 元组 |
|---|
| 元素数量 | 不固定 | 固定 |
| 元素类型 | 通常一致 | 位置可以不同 |
| 使用场景 | 列表、集合 | 固定位置的数据组合 |
| 类型写法 | T[] 或 Array<T> | [T, U, V] |
技巧
一个简单的判断标准:如果数据用位置来区分含义(比如第一个是经度、第二个是纬度),用元组。如果数据是同一类事物的集合(比如所有名字),用数组。
数组和元组让你能描述"一组数据"了。
但真实世界的数据远不止线性列表。人要有名字、年龄、地址…… 下一篇,我们用对象和接口来描述复杂数据结构。