TypeScript 的数组类型就是元素类型后面加上 []:string[] 是字符串数组,number[] 是数字数组。泛型写法 Array<string> 是同一个类型。数组一旦有了类型,你添加和读取的每个元素都具有这个类型。
@ts-expect-error 那一行是一个编译错误(TS2345)。这里把它标记为预期错误,代码块才能继续运行;而且类型在运行时会被擦除,所以 42 确实被添加进了数组,输出里能看到它。
string[] 与 Array<string>
| 写法 | 等价于 | 说明 |
|---|---|---|
string[] | Array<string> | 最常见的写法。 |
(string | number)[] | Array<string | number> | 括号不能省略:string | number[] 的意思是“一个字符串,或者一个数字数组”。 |
readonly string[] | ReadonlyArray<string> | 没有 push、pop、sort,也不能按索引赋值。 |
User[] | Array<User> | 对象数组使用对象的类型。 |
string[][] | Array<Array<string>> | 二维数组(网格)。 |
一个代码库里选定一种风格即可。typescript-eslint 的 array-type 规则默认使用 T[]。
对象数组
用类型别名或接口描述元素,然后使用 Type[]。从数组中读出的所有内容都会按这个结构进行检查。
添加到 users 中的对象字面量必须与 User 完全匹配:缺少 admin 或者属性名拼错都是编译错误。
带类型的 map、filter、reduce 和 find
数组方法都是泛型的,所以它们的结果也带有类型。每个方法在运行时做什么,在 JavaScript 的数组方法页面中讲解;类型是 TypeScript 额外加上的部分:
| 方法 | 在 T[] 上的结果类型 |
|---|---|
map(fn) | U[],其中 U 是 fn 的返回类型 |
filter(fn) | T[](或更窄的类型,见下文) |
find(fn) | T | undefined |
findIndex(fn)、indexOf(x) | number(找不到时为 -1) |
some(fn)、every(fn)、includes(x) | boolean |
reduce(fn, init) | init 的类型(或类型参数,reduce<R>(...)) |
at(i) | T | undefined |
join(sep) | string |
最后一个例子之所以成立,是因为 TypeScript(从 5.5 起)能推断出 (n) => n !== undefined 是一个类型谓词,所以 filter 返回 number[],而不是 (number | undefined)[]。对于它推断不出来的检查,自己写出谓词:filter((x): x is User => x !== null)。
包含多种类型的数组
联合类型作为元素类型时允许混合。数组类型的联合则不允许:
如果每个位置的类型是固定的,比如 [name, age] 这样的一对值,就改用元组:[string, number] 知道索引 0 是字符串、索引 1 是数字,而 (string | number)[] 不知道。
只读数组
readonly T[] 去掉了所有会修改数组的方法。函数不应修改的参数和常量都可以用它。
index.ts(3,12): error TS2339: Property 'push' does not exist on type 'readonly number[]'.
删掉 push 那一行,代码块就会打印 4。在需要 readonly number[] 的地方,总是可以传入可变的 number[],所以只读参数不会给调用者带来任何成本。这个检查只在编译时进行:运行时它就是一个普通数组。要对只读数组排序,就排序它的副本:[...values].sort()。
字面量数组上 includes 的陷阱
as const 把数组变成由字面量类型组成的只读元组。用它表示允许值的列表很方便,但这样一来它的 includes 只接受这些字面量:
把检查包装进类型守卫(value is Color)意味着类型放宽只发生一次,调用者拿回的是一个已经收窄的值。
索引访问和空数组
读取 arr[i] 得到的类型是 T,即使 i 超出范围、运行时的值是 undefined 也一样。at(i) 的类型是 T | undefined,而编译选项 noUncheckedIndexedAccess 会让普通的索引访问也返回 T | undefined。
queue[0].toUpperCase() 能通过编译,但运行时会抛出 TypeError。当索引可能不存在时,优先使用 at()、先检查长度,或者开启 noUncheckedIndexedAccess。
常见问题
TypeScript 中如何声明数组类型?
在元素类型后面加上 []:let names: string[] = ["a", "b"]。泛型写法 Array<string> 的含义完全相同。对象数组则用对象类型或接口作为元素类型:User[]。
string[] 和 Array<string> 有什么区别?
没有区别:它们是同一个类型的两种写法。string[] 更常见。元素类型比较复杂时,泛型写法更好读;只读数组也一样,readonly string[] 和 ReadonlyArray<string> 是同一个类型。
为什么 TypeScript 中 find 会返回 undefined?
array.find() 返回 T | undefined,因为可能没有任何元素匹配。在 strict 下,使用结果之前必须处理 undefined 的情况:用 if 检查、可选链(found?.name)或默认值(found ?? fallback)。
TypeScript 中如何为包含多种类型的数组标注类型?
把联合类型放在括号里作为元素类型:(string | number)[] 是一个每个元素都是字符串或数字的数组。它和 string[] | number[] 不同,后者要么是只有字符串的数组,要么是只有数字的数组。如果各个位置的类型固定,比如 [string, number],就用元组。
为什么在 as const 数组上调用 includes 会报错?
字面量组成的只读数组,比如 ["red", "green"] as const,它的方法是 includes(searchElement: "red" | "green"),所以传入普通的 string 会报错 TS2345。检查时把数组的类型放宽:(COLORS as readonly string[]).includes(input),最好放在一个类型守卫里,把 input 收窄为字面量联合类型。