数组与元组:有序数据的集合

目录

第 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]
技巧

一个简单的判断标准:如果数据用位置来区分含义(比如第一个是经度、第二个是纬度),用元组。如果数据是同一类事物的集合(比如所有名字),用数组。


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

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

目录