Skip to content

数组与元组

数组是编程中最常用的数据结构之一。TypeScript 为数组提供了丰富的类型支持,确保数组元素的类型一致性。元组则是 TypeScript 特有的类型,用于表示固定长度和类型的数组。

数组类型#

基本语法#

TypeScript 中定义数组有两种等价的语法:

// TypeScript 5.x
// 语法一:类型[](推荐)
const numbers: number[] = [1, 2, 3, 4, 5]
const names: string[] = ['张三', '李四', '王五']
const flags: boolean[] = [true, false, true]
// 语法二:Array<类型>(泛型语法)
const scores: Array<number> = [95, 88, 92]
const users: Array<string> = ['user1', 'user2']

🤔 两种语法效果完全相同,type[] 更简洁,Array<type> 在复杂类型时可读性更好。

类型推断#

初始化时 TypeScript 会自动推断数组类型:

// 自动推断为 number[]
const nums = [1, 2, 3]
// 自动推断为 string[]
const strs = ['a', 'b', 'c']
// 自动推断为 (string | number)[]
const mixed = [1, 'hello', 2, 'world']

空数组需要显式标注#

// ❌ 推断为 never[],无法添加元素
const items = []
// items.push('hello'); // 错误
// ✅ 显式标注类型
const items2: string[] = []
items2.push('hello') // 正确
// ✅ 或者初始化时提供元素
const items3 = ['initial']
items3.push('hello') // 正确

多维数组#

// 二维数组
const matrix: number[][] = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
// 三维数组
const cube: number[][][] = [
[
[1, 2],
[3, 4],
],
[
[5, 6],
[7, 8],
],
]
// 使用泛型语法
const grid: Array<Array<number>> = [
[1, 2],
[3, 4],
]

联合类型数组#

数组元素可以是多种类型:

// 数字或字符串数组
const ids: (number | string)[] = [1, '2', 3, 'four']
// 注意括号位置
// (number | string)[] - 数组,元素是 number 或 string
// number | string[] - number 或 string数组(这可能不是你想要的)
// 包含 null 的数组
const nullableItems: (string | null)[] = ['hello', null, 'world']
// 对象数组
interface User {
id: number
name: string
}
const users: User[] = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
]

只读数组#

readonly 修饰符#

// 只读数组,不能修改
const readonlyNums: readonly number[] = [1, 2, 3]
// ❌ 以下操作都会报错
// readonlyNums.push(4);
// readonlyNums.pop();
// readonlyNums[0] = 10;
// ✅ 可以读取
console.log(readonlyNums[0]) // 1
console.log(readonlyNums.length) // 3

ReadonlyArray 泛型#

const readonlyItems: ReadonlyArray<string> = ['a', 'b', 'c']
// 等价于
const readonlyItems2: readonly string[] = ['a', 'b', 'c']

🎯 只读数组的使用场景:

// 场景1:函数参数,防止意外修改
function processItems(items: readonly string[]) {
// items.push('new'); // ❌ 错误
return items.map((item) => item.toUpperCase()) // ✅ 返回新数组
}
// 场景2:配置常量
const ALLOWED_METHODS: readonly string[] = ['GET', 'POST', 'PUT', 'DELETE']
// 场景3:不可变数据模式
function addItem(items: readonly number[], newItem: number): readonly number[] {
return [...items, newItem] // 返回新数组
}

元组(Tuple)#

元组是固定长度和类型的数组,每个位置的类型可以不同。

基本用法#

// 定义元组类型
let point: [number, number] = [10, 20]
let person: [string, number] = ['张三', 25]
let record: [number, string, boolean] = [1, 'active', true]
// 访问元素
console.log(point[0]) // 10
console.log(person[1]) // 25
// 解构赋值
const [x, y] = point
const [name, age] = person

元组 vs 数组#

// 数组:长度可变,所有元素同类型
const numbers: number[] = [1, 2, 3]
numbers.push(4) // ✅ 可以添加
// 元组:固定长度,每个位置类型确定
const pair: [string, number] = ['hello', 42]
// pair.push('extra'); // TypeScript 允许,但逻辑上不推荐
// pair[2]; // ❌ 错误:长度为 2 的元组没有索引 2

🔶 元组的一个坑:TypeScript 允许对元组使用 push 等方法,但这会破坏元组的固定长度语义。

const tuple: [string, number] = ['hello', 1]
tuple.push('extra') // 不报错!
console.log(tuple) // ['hello', 1, 'extra']
// 但 tuple[2] 仍然会报错

可选元素#

// 第三个元素可选
type Point2DOrPoint3D = [number, number, number?]
const point2D: Point2DOrPoint3D = [1, 2]
const point3D: Point2DOrPoint3D = [1, 2, 3]
// 可选元素必须在末尾
type HttpResponse = [number, string, object?]
const success: HttpResponse = [200, 'OK', { data: [] }]
const notFound: HttpResponse = [404, 'Not Found']

剩余元素#

// 前两个是固定类型,后面可以有任意多个字符串
type StringList = [number, number, ...string[]]
const list1: StringList = [1, 2]
const list2: StringList = [1, 2, 'a']
const list3: StringList = [1, 2, 'a', 'b', 'c']
// 实际应用:函数参数
type Args = [string, ...number[]]
function sum(label: string, ...nums: number[]): void {
console.log(
label,
nums.reduce((a, b) => a + b, 0)
)
}
sum('Total:', 1, 2, 3, 4, 5)

只读元组#

// 只读元组
const point: readonly [number, number] = [10, 20]
// point[0] = 30; // ❌ 错误
// 使用 as const 创建只读元组
const colors = ['red', 'green', 'blue'] as const
// 类型是 readonly ["red", "green", "blue"]
// colors.push('yellow'); // ❌ 错误
// colors[0] = 'pink'; // ❌ 错误

命名元组(TypeScript 4.0+)#

// 给元组元素命名,提高可读性
type Person = [name: string, age: number, active: boolean]
const user: Person = ['张三', 25, true]
// 命名主要用于文档和提示,不影响实际使用
function createPerson(...args: Person): void {
const [name, age, active] = args
console.log(name, age, active)
}

实际应用场景#

场景一:函数返回多个值#

// 使用元组返回多个值
function useState<T>(initial: T): [T, (value: T) => void] {
let state = initial
const setState = (value: T) => {
state = value
}
return [state, setState]
}
// 使用时解构
const [count, setCount] = useState(0)
const [name, setName] = useState('张三')

场景二:坐标和范围#

type Point = [x: number, y: number]
type Range = [start: number, end: number]
type RGB = [red: number, green: number, blue: number]
const origin: Point = [0, 0]
const range: Range = [1, 100]
const color: RGB = [255, 128, 0]
function distance(p1: Point, p2: Point): number {
const [x1, y1] = p1
const [x2, y2] = p2
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
}

场景三:键值对#

type Entry<K, V> = [key: K, value: V]
const entries: Entry<string, number>[] = [
['one', 1],
['two', 2],
['three', 3],
]
// 配合 Object.entries 使用
const obj = { a: 1, b: 2, c: 3 }
const pairs: [string, number][] = Object.entries(obj)

数组常用操作的类型#

const numbers: number[] = [1, 2, 3, 4, 5]
// map:转换类型
const strings: string[] = numbers.map((n) => n.toString())
// filter:保持类型
const evens: number[] = numbers.filter((n) => n % 2 === 0)
// reduce:指定返回类型
const sum: number = numbers.reduce((acc, n) => acc + n, 0)
// find:返回 T | undefined
const found: number | undefined = numbers.find((n) => n > 3)
// 类型守卫过滤
const mixed: (string | null)[] = ['a', null, 'b', null, 'c']
const filtered: string[] = mixed.filter((x): x is string => x !== null)

常见问题#

🙋 元组和数组有什么本质区别?#

// 数组适合:同类型的列表
const scores: number[] = [90, 85, 88]
// 元组适合:结构化的固定数据
const user: [number, string, boolean] = [1, '张三', true]

🙋 为什么空数组推断为 never[]?#

因为 TypeScript 无法从空数组推断元素类型:

const arr = [] // never[]
// 解决方案
const arr1: string[] = []
const arr2 = [] as string[]
const arr3: Array<string> = []

🙋 as const 有什么作用?#

as const 会将数组转换为只读的字面量类型元组:

// 普通数组
const arr1 = [1, 2, 3] // number[]
// as const
const arr2 = [1, 2, 3] as const // readonly [1, 2, 3]
// 常用于定义常量
const DIRECTIONS = ['up', 'down', 'left', 'right'] as const
type Direction = (typeof DIRECTIONS)[number] // "up" | "down" | "left" | "right"

总结#

类型语法特点
数组T[]Array<T>可变长度,同类型元素
只读数组readonly T[]不可修改
元组[T1, T2, ...]固定长度,每位置类型确定
只读元组readonly [T1, T2]不可修改的元组

下一篇我们将学习对象类型,了解如何描述复杂的对象结构。