数组类型

数组是十分常用的数据结构,它表示一组有序元素的集合。在 TypeScript 中,数组值的数据类型为数组类型。

数组类型定义

TypeScript 提供了以下两种方式来定义数组类型:

  • 简便数组类型表示法。

  • 泛型数组类型表示法。

以上两种数组类型定义方式仅在编码风格上有所区别,两者在功能上没有任何差别。

简便数组类型表示法

简便数组类型表示法借用了数组字面量的语法,通过在数组元素类型之后添加一对方括号 “[]” 来定义数组类型。它的语法如下所示:

TElement[]

该语法中,TElement 代表数组元素的类型,方括号 [] 代表数组类型。在 TElement[] 之间不允许出现换行符号。

下例中,我们使用 number[] 类型注解定义了常量 digits 的类型为 number 数组类型,它表示 digits 数组中元素的类型为 number 类型。示例如下:

const digits: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

如果数组中元素的类型为复合类型,则需要在数组元素类型上使用分组运算符,即小括号。例如,下例中的 red 数组既包含字符串元素也包含数字元素。因此,red 数组元素的类型为 string 类型和 number 类型构成的联合类型,即 string |number 。在使用简便数组类型表示法时,必须先将联合类型放在分组运算符内,然后再在后面添加一对方括号。示例如下:

const red: (string | number)[] = ['f', f, 0, 0, 0, 0];

此例中,若在类型注解里没有使用分组运算符,则表示 string 类型和 number[] 类型的联合类型,即 string | (number[])。该类型与实际数组类型不兼容,因此将产生编译错误。示例如下:

const red: string | number[] = ['f', 'f', 0, 0, 0, 0];
//    ~~~
//    编译错误

泛型数组类型表示法

泛型数组类型表示法是另一种表示数组类型的方法。顾名思义,泛型数组类型表示法就是使用泛型来表示数组类型。它的语法如下所示:

Array<TElement>

该语法中,Array 代表数组类型;<TElement> 是类型参数的语法,其中 TElement 代表数组元素的类型。关于泛型的详细介绍请参考 6.1 节。

下例中,我们使用 Array<number> 类型注解定义了常量 digits 的类型为 number 数组类型,它表示 digits 数组中元素的类型为 number 类型。示例如下:

const digits: Array<number> = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

在使用泛型数组类型表示法时,就算数组中元素的类型为复合类型也不需要使用分组运算符。我们还是以既包含字符串元素也包含数字元素的 red 数组为例,示例如下:

const red: Array<string | number> = ['f', 'f', 0, 0, 0, 0];

此例中,我们不再需要对联合类型 string | number 使用分组运算符。

两种方法比较

正如前文所讲,简便数组类型表示法和泛型数组类型表示法在功能上没有任何差别,两者只是在编程风格上有所差别。

在定义简单数组类型时,如数组元素为单一原始类型或类型引用,使用简便数组类型表示法更加清晰和简洁。示例如下:

let a: string[];

let b: HTMLButtonElement[];

如果数组元素是复杂类型,如对象类型和联合类型等,则可以选择使用泛型数组类型表示法。它也许能让代码看起来更加整洁一些。示例如下:

let a: Array<string | number>;

let b: Array<{ x: number; y: number }>;

总结起来,目前存在以下三种常见的编码风格供读者参考:

  • 始终使用简便数组类型表示法。

  • 始终使用泛型数组类型表示法。

  • 当数组元素类型为单一原始类型或类型引用时,始终使用简便数组类型表示法;在其他情况下不做限制。

数组元素类型

在定义了数组类型之后,当访问数组元素时能够获得正确的元素类型信息。示例如下:

const digits: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

const zero = digits[0];
//    ~~~~
//    number类型

此例中,虽然没有给常量 zero 添加类型注解,但是 TypeScript 编译器能够从数组类型中推断出 zero 的类型为 number 类型。

我们知道,当访问数组中不存在的元素时将返回 undefined 值。TypeScript 的类型系统无法推断出是否存在数组访问越界的情况,因此即使访问了不存在的数组元素,还是会得到声明的数组元素类型。示例如下:

const digits: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

// 没有编译错误
const out: number = digits[100];

只读数组

只读数组与常规数组的区别在于,只读数组仅允许程序读取数组元素而不允许修改数组元素。

TypeScript 提供了以下三种方式来定义一个只读数组:

  • 使用 ReadonlyArray<T> 内置类型。

  • 使用 readonly 修饰符。

  • 使用 Readonly<T> 工具类型。

以上三种定义只读数组的方式只是语法不同,它们在功能上没有任何差别。

ReadonlyArray<T>

TypeScript 早期版本中,提供了 ReadonlyArray<T> 类型专门用于定义只读数组。在该类型中,类型参数 T 表示数组元素的类型。示例如下:

const red: ReadonlyArray<number> = [255, 0, 0];

此例中,定义了只读数组 red,该数组中元素的类型为 number

readonly

TypeScript 3.4 版本中引入了一种新语法,使用 readonly 修饰符能够定义只读数组。在定义只读数组时,将 readonly 修饰符置于数组类型之前即可。示例如下:

const red: readonly number[] = [255, 0, 0];

注意,readonly 修饰符不允许与泛型数组类型表示法一起使用。示例如下:

const red: readonly Array<number> = [255, 0, 0];
//         ~~~~~~~~
//         编译错误

Readonly<T>

Readonly<T>TypeScript 提供的一个内置工具类型,用于定义只读对象类型。该工具类型能够将类型参数 T 的所有属性转换为只读属性,它的定义如下所示:

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

由于 TypeScript 3.4 支持了使用 readonly 修饰符来定义只读数组,所以从 TypeScript 3.4 开始可以使用 Readonly<T> 工具类型来定义只读数组。示例如下:

const red: Readonly<number[]> = [255, 0, 0];

需要注意的是,类型参数 T 的值为数组类型 number[],而不是数组元素类型 number。在这一点上,它与 ReadonlyArray<T> 类型是有区别的。

注意事项

我们可以通过数组元素索引来访问只读数组元素,但是不能修改只读数组元素。示例如下:

const red: readonly number[] = [255, 0, 0];

red[0];         // 正确

red[0] = 0;     // 编译错误

在只读数组上也不支持任何能够修改数组元素的方法,如 pushpop 方法等。示例如下:

const red: readonly number[] = [255, 0, 0];

red.push(0);     // 编译错误
red.pop();       // 编译错误

在进行赋值操作时,允许将常规数组类型赋值给只读数组类型,但是不允许将只读数组类型赋值给常规数组类型。换句话说,不能通过赋值操作来放宽对只读数组的约束。示例如下:

const a: number[] = [0];
const ra: readonly number[] = [0];

const x: readonly number[] = a;  // 正确

const y: number[] = ra;          // 编译错误