# 数组与元组：有序数据的集合

## 概述

前三篇我们学了基础类型和类型运算。现在能描述单个值了。

但真实世界的程序处理的是**批量数据**——用户列表、时间坐标、键值对……

TypeScript 提供两种结构来处理有序集合：

- **数组**——同类型元素的列表
- **元组**——固定数量和位置的组合

## 数组类型

数组有两种写法，意思完全一样：

```typescript
const arr1: string[] = ["a", "b", "c"]
const arr2: Array<string> = ["a", "b", "c"]
```

第一种 `T[]` 更常用，读起来像"字符串的数组"。第二种 `Array<T>` 是泛型写法，之后会详讲。

```typescript
const numbers: number[] = [1, 2, 3]
const booleans: boolean[] = [true, false]
const mixed: (string | number)[] = ["a", 1, "b", 2]
```

数组的元素没有数量限制：

```typescript
const names: string[] = []
const fruits: string[] = ["苹果", "香蕉"]
const many: string[] = new Array(100).fill("x")
```

### readonly 数组

普通数组可以修改元素或长度。如果不希望被修改，可以用 `readonly`：

```typescript
const arr: readonly string[] = ["a", "b", "c"]

arr[0] = "x"     // ❌ 只读，不能修改
arr.push("d")    // ❌ 只读，不能添加
```

只读数组在函数参数中很有用——告诉调用者"我不会改你的数据"：

```typescript
function logItems(items: readonly string[]) {
  items.push("d")     // ❌ 编译报错，安全
  console.log(items)
}
```

`ReadonlyArray<T>` 和 `readonly T[]` 等价：

```typescript
const arr1: readonly string[] = ["a", "b"]
const arr2: ReadonlyArray<string> = ["a", "b"]
```

## 元组

数组的元素类型一致、数量不限。但有些数据天然就是固定结构的——比如经纬度、二维坐标：

```typescript
// 用数组不够精确
const position: number[] = [121.5, 31.2] // 谁知道哪个是经度哪个是纬度？
```

**元组（Tuple）** 就是解决这个问题的。它限定了元素的**数量和类型**：

```typescript
type Coordinate = [number, number]       // 经度，纬度
type Pair = [string, number]             // 名字，年龄

const position: Coordinate = [121.5, 31.2]
const item: Pair = ["张三", 18]
```

元组的位置是有意义的。第一个和第二个的类型可以不同：

```typescript
type HttpResult = [number, string]  // [状态码, 消息]

const success: HttpResult = [200, "OK"]
const notFound: HttpResult = [404, "Not Found"]
```

### 可选元素

元组的某些位置可以标记为可选：

```typescript
type Count = [string, number?]

const c1: Count = ["数量"]
const c2: Count = ["数量", 10]
```

### 元组标签

TypeScript 4.0 后，可以为元组的每个位置加标签，说明其含义：

```typescript
type Coordinate = [longitude: number, latitude: number]

function getPosition(): Coordinate {
  return [121.5, 31.2]
}

const [lng, lat] = getPosition()
// 悬停查看：lng 显示为 longitude，lat 显示为 latitude
```

标签在开发工具中会显示出来，提高代码可读性。

### 元组的实际用途

元组在前端开发中非常常见。

**React 的 useState：**

```typescript
// useState 返回的就是元组
const [count, setCount] = useState(0)
// const [state, setState] = useState<number>(initialValue)
// 类型是 [number, Dispatch<SetStateAction<number>>]
```

**解构赋值的类型安全：**

```typescript
function splitName(fullName: string): [string, string] {
  const parts = fullName.split(" ")
  return [parts[0], parts[1] || ""]
}

const [first, last] = splitName("张三")
// first 的类型是 string，last 的类型也是 string
```

**变长元组（Rest 元素）：**

```typescript
type StringWithNumbers = [string, ...number[]]

const a: StringWithNumbers = ["hello", 1, 2, 3]
const b: StringWithNumbers = ["hello"]

type HeadAndTail = [string, ...boolean[]]
// 第一个是 string，后面全是 boolean
```

## 数组 vs 元组

| 对比 | 数组 | 元组 |
|------|------|------|
| 元素数量 | 不固定 | 固定 |
| 元素类型 | 通常一致 | 位置可以不同 |
| 使用场景 | 列表、集合 | 固定位置的数据组合 |
| 类型写法 | `T[]` 或 `Array<T>` | `[T, U, V]` |

> [!tip] 技巧
> 一个简单的判断标准：如果数据用位置来区分含义（比如第一个是经度、第二个是纬度），用元组。如果数据是同一类事物的集合（比如所有名字），用数组。

---

数组和元组让你能描述"一组数据"了。

但真实世界的数据远不止线性列表。**人要有名字、年龄、地址……** 下一篇，我们用对象和接口来描述复杂数据结构。


---

> 作者: Aphros  
> URL: https://blog.papergate.top/posts/04.%E6%95%B0%E7%BB%84%E4%B8%8E%E5%85%83%E7%BB%84%E6%9C%89%E5%BA%8F%E6%95%B0%E6%8D%AE%E7%9A%84%E9%9B%86%E5%90%88/  

