《Go 后端开发者学 TypeScript:数组和元组》

0 阅读4分钟

在 TypeScript 中,数组(Array)和元组(Tuple)最大的区别是:数组通常只约束元素类型,而元组还可以约束元素的数量、顺序以及每个位置的类型。

你之前主要写 Go 后端,我们直接结合 Go 的数组、切片和结构体来理解。

一、先看数组(Array)

TypeScript 数组的写法:

const numbers: number[] = [1, 2, 3];

const names: string[] = ["张三", "李四", "王五"];

解释:

  • number[]:数组元素必须是数字。
  • string[]:数组元素必须是字符串。
  • 数组长度可以动态变化。

例如:

const numbers: number[] = [1, 2, 3];

numbers.push(4);    // 正确
numbers.push(5);    // 正确
numbers.push("6");  // 类型错误

这里 numbers 最终变成:

[1, 2, 3, 4, 5]

你可以把 TypeScript 数组类比为 Go 的切片:

numbers := []int{1, 2, 3}
numbers = append(numbers, 4)

注意:TypeScript 的 Array 更接近 Go 的 slice,而不是 Go 的固定长度数组 [3]int。

二、再看元组(Tuple)

元组的写法:

const user: [number, string] = [1001, "张三"];

解释:

[number, string]

规定了:

  • 第一个元素必须是 number。
  • 第二个元素必须是 string。
  • 这个元组正常赋值时必须有两个元素。

例如:

const user: [number, string] = [1001, "张三"]; // 正确

const user2: [number, string] = ["张三", 1001]; // 错误:顺序不对

const user3: [number, string] = [1001]; // 错误:缺少元素

const user4: [number, string] = [1001, "张三", 18]; // 错误:元素过多

所以你可以理解成:

元组就是对数组中的每个位置都规定了类型的特殊数组。

三、数组和元组的直观对比

数组 Array

number[]

元素 1

10

number

元素 2

20

number

元素 3

30

number

元素 4

40

number

长度可变,元素类型相同

元组 Tuple

[number, string]

位置 0

1001

number

位置 1

张三

string

位置和类型都有约束

四、一个容易误解的地方:数组也能存不同类型

例如:

const data: (number | string)[] = [1001, "张三", 18];

这也是合法的数组。

那么和元组有什么区别?

// 数组
const data: (number | string)[] = [1001, "张三"];

// 元组
const user: [number, string] = [1001, "张三"];

对比:

data[0] = "李四"; // 正确
data[1] = 2002;   // 正确

user[0] = "李四"; // 类型错误
user[1] = 2002;   // 类型错误

原因:

  • 数组 (number | string)[]:任意位置都可以是数字或者字符串。
  • 元组 [number, string]:第一个位置必须是数字,第二个位置必须是字符串。

五、元组的典型应用:函数返回多个值

你写过 Go,应该比较熟悉多返回值:

func getUser() (int64, string) {
    return 1001, "张三"
}

func main() {
    id, name := getUser()
    fmt.Println(id, name)
}

TypeScript 没有 Go 那样的原生多返回值机制,但可以通过元组实现类似效果:

function getUser(): [number, string] {
    return [1001, "张三"];
}

const [id, name] = getUser();

console.log(id);   // 1001
console.log(name); // 张三

这里:

const [id, name] = getUser();

叫作数组解构赋值。

你可以理解为从元组中取出第一个和第二个元素,分别赋值给 id 和 name。

六、元组还有一些特殊用法

1. 可选元素

let user: [number, string?];

user = [1001];         // 正确
user = [1001, "张三"]; // 正确

string? 表示第二个元素可以不存在。

2. 带名称的元组

type User = [id: number, name: string];

const user: User = [1001, "张三"];

这里的 id 和 name 只是提高可读性的标签,并不会把元组变成对象。

访问方式仍然是:

console.log(user[0]); // 1001
console.log(user[1]); // 张三

3. readonly 元组

还有一个容易踩坑的地方:普通元组在运行时仍然是 JavaScript 数组。

例如:

let user: [number, string] = [1001, "张三"];

user.push("李四"); // TypeScript 允许

因此,普通元组并不等于运行时不可改变的固定长度数组。

如果不希望通过 push 修改,可以使用:

const user: readonly [number, string] = [1001, "张三"];

user.push("李四"); // 类型错误

readonly 是 TypeScript 的编译期约束,并不自动冻结运行时对象。

七、最后总结

对比数组 Array元组 Tuple
定义方式number[][number, string]
元素类型通常统一,也可以是联合类型可以按位置指定不同类型
元素顺序不限制各位置的具体类型有类型顺序约束
长度通常可变类型层面可约束长度
使用场景数据列表、批量记录固定结构、函数多个返回值
Go 类比[]int 切片类似固定结构的多返回值

实际开发建议:

  • 一批任务 ID:使用 number[]。
  • 一批用户名称:使用 string[]。
  • 一个函数需要返回 [任务ID, 是否成功]:可以使用 [number, boolean]。
  • 一个用户包含很多字段:通常优先使用 interface 或 type 定义对象,而不是使用很长的元组。

一句话记忆:数组关注“装什么类型的数据”,元组关注“每个位置应该放什么数据”。