Menu

TypeScript 数组:类型、对象数组、map 和 filter

TypeScript 中如何为数组标注类型:T[] 与 Array<T>、对象数组、包含多种类型的数组、只读数组,以及 map、filter、reduce 和 find 的返回类型。还有字面量数组上 includes 的陷阱。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

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 收窄为字面量联合类型。

Coddy 编程语言插图

和 Coddy 一起学编程

开始