引言:为什么单一类型很无趣?
\n\n想象一下:你正在用 TypeScript 编写代码,突然遇到一种情况,数组需要存储不同类型的数据。例如,一个列表,其中每个元素要么是 string,要么是 number。或者一个由姓名(字符串)和年龄(数字)组成的元组。
许多新手(以及不仅仅是新手)都会问这样一个问题:“如何在 TypeScript 中声明一个包含多种类型的数组?” 这个问题在 Stack Overflow 上获得了数千次浏览——这并非没有原因。TypeScript 对类型要求严格,但同时也为这种情况提供了灵活的工具。
\n\n在本文中,我们将探讨所有处理混合类型数组的方法:从简单的联合类型到元组和泛型。您将了解何时使用哪种方法,以及如何避免陷入 any 的陷阱。
1. 联合类型:最简单的方法
\n\n最明显的选择是使用联合类型。这就像是在说:“数组可以包含 string 类型或 number 类型的元素”。
// 可以包含字符串或数字的数组\nconst mixedArray: (string | number)[] = ['hello', 42, 'world', 100];\n\n// 另一种写法(等价)\nconst mixedArray2: Array<string | number> = ['hello', 42, 'world', 100];\n\n这里的 (string | number)[] 意思是:“一个数组,其每个元素都是字符串或数字”。TypeScript 会检查你是否试图添加,比如说,布尔值或对象。
什么时候用这个方便?
\n- \n
- 当元素的顺序不重要,只有允许的类型集合重要时。 \n
- 当你处理一个返回异构数据数组的 API 时。 \n
- 当你在同一个数组中存储 ID(数字)和姓名(字符串)列表时。 \n
陷阱
\n使用联合类型时,你无法保证第一个位置是字符串,第二个位置是数字。类型化只说明:“数组中有字符串和数字,但顺序未知”。
\n\nconst arr: (string | number)[] = [1, 'hello'];\n// TypeScript 不知道:arr[0] 是 number 还是 string?\nconst first = arr[0]; // 类型:string | number\n// 要将其用作数字,需要缩小类型:\nif (typeof first === 'number') {\n console.log(first.toFixed(2));\n}\n\n2. 元组:当顺序很重要时
\n\n如果你需要数组具有固定的结构——例如,第一个是字符串,第二个是数字,第三个是布尔值——请使用元组。
\n\n// 元组:[字符串, 数字, 布尔值]\nconst person: [string, number, boolean] = ['Alice', 30, true];\n\n// 带标签的元组——更具可读性\nconst product: [name: string, price: number, inStock: boolean] = ['Laptop', 1200, true];\n\n元组严格固定了元素的数量和每个位置的类型。如果你试图分配错误类型的值或添加多余的元素,TypeScript 会报错。
\n\n元组中的剩余元素
\n从 TypeScript 4.0 开始,可以在元组中指定剩余元素。当部分结构固定,其余部分可变时,这很方便:
\n\n// 第一个元素是字符串,其余是数字\nconst data: [string, ...number[]] = ['scores', 95, 87, 92];\n\n// 可以组合使用\nconst logEntry: [timestamp: Date, ...messages: string[]] = [new Date(), 'info', 'user logged in'];\n\n3. 复杂数组的类型别名
\n\n当数组结构变得复杂时,将类型提取到单独的别名中会很方便。这提高了代码的可读性,并允许重用该类型。
\n\ntype UserEntry = [id: number, name: string, email: string, isActive: boolean];\n\nconst users: UserE