联合类型

联合类型由一组有序的成员类型构成。联合类型表示一个值的类型可以为若干种类型之一。例如,联合类型 string | number 表示一个值的类型既可以为 string 类型也可以为 number 类型。

联合类型通过联合类型字面量来定义。

联合类型字面量

联合类型由两个或两个以上的成员类型构成,各成员类型之间使用竖线符号 | 分隔。示例如下:

type NumericType = number | bigint;

此例中,定义了一个名为 NumericType 的联合类型。该联合类型由两个成员类型组成,即 number 类型和 bigint 类型。若一个值既可能为 number 类型又可能为 bigint 类型,那么我们说该值的类型为联合类型 number | bigint

联合类型的成员类型可以为任意类型,如原始类型、数组类型、对象类型,以及函数类型等。示例如下:

type T = boolean | string[] | { x: number } | (() => void);

如果联合类型中存在相同的成员类型,那么相同的成员类型将被合并为单一成员类型。例如,下例中的类型别名 T0T1 都表示 boolean 类型,类型别名 T2T3 都表示同一种联合类型 boolean | string

type T0 = boolean;                     // boolean
type T1 = boolean | boolean;           // boolean

type T2 = boolean | string;            // boolean | string
type T3 = boolean | string | boolean;  // boolean | string

联合类型是有序的成员类型的集合。在绝大部分情况下,成员类型满足类似于数学中的 加法交换律,即改变成员类型的顺序不影响联合类型的结果类型。下例中,虽然联合类型 T0T1 中成员类型的顺序不同,但是 T0T1 表示相同的类型:

type T0 = string | number;
type T1 = number | string;

联合类型中的类型成员同样满足类似于数学中的 加法结合律。对部分类型成员使用分组运算符不影响联合类型的结果类型。例如,下例中的 T0T1 类型是同一个类型:

type T0 = (boolean | string) | number;
type T1 = boolean | (string | number);

联合类型的成员类型可以进行化简。假设有联合类型 U = T0 | T1,如果 T1T0 的子类型,那么可以将类型成员 T1 从联合类型 U 中消去。最后,联合类型 U 的结果类型为 U = T0。例如,有联合类型 boolean | true | false。其中,true 类型和 false 类型是 boolean 类型的子类型,因此可以将 true 类型和 false 类型从联合类型中消去。最终,联合类型 boolean | true | false 的结果类型为 boolean 类型。示例如下:

// 'true' 和 'false' 类型是 'boolean' 类型的子类型
type T0 = boolean | true | false;

// 所以T0等同于 T1
type T1 = boolean;

关于子类型的详细介绍请参考 7.1 节。

联合类型的类型成员

像接口类型一样,联合类型作为一个整体也可以有类型成员,只不过联合类型的类型成员是由其成员类型决定的。

属性签名

若联合类型 U 中的每个成员类型都包含一个同名的属性签名 M,那么联合类型 U 也包含属性签名 M。例如,有如下定义的 Circle 类型与 Rectangle 类型,以及由这两个类型构成的联合类型 Shape

interface Circle {
    area: number;
    radius: number;
}

interface Rectangle {
    area: number;
    width: number;
    height: number;
}

type Shape = Circle | Rectangle;

此例中,因为 Circle 类型与 Rectangle 类型均包含名为 area 的属性签名类型成员,所以联合类型 Shape 也包含名为 area 的属性签名类型成员。因此,允许访问 Shape 类型上的 area 属性。示例如下:

type Shape = Circle | Rectangle;

declare const s: Shape;

s.area; // number

因为 radiuswidthheight 类型成员不是 Circle 类型和 Rectangle 类型的共同类型成员,因此它们不是 Shape 联合类型的类型成员。示例如下:

type Shape = Circle | Rectangle;

declare const s: Shape;

s.radius; // 错误
s.width;  // 错误
s.height; // 错误

对于联合类型的属性签名,其类型为所有成员类型中该属性类型的联合类型。例如,下例中联合类型 Circle | Rectangle 具有属性签名 area,其类型为 Circle 类型中 area 属性的类型和 Rectangle 类型中 area 属性的类型组成的联合类型,即 bigint |number 类型。示例如下:

interface Circle {
    area: bigint;
}

interface Rectangle {
    area: number;
}

declare const s: Circle | Rectangle;

s.area;   // bigint | number

如果联合类型的属性签名在某个成员类型中是可选属性签名,那么该属性签名在联合类型中也是可选属性签名;否则,该属性签名在联合类型中是必选属性签名。示例如下:

interface Circle {
    area: bigint;
}

interface Rectangle {
    area?: number;
}

declare const s: Circle | Rectangle;

s.area; // bigint | number | undefined

此例中,area 属性在 Rectangle 类型中是可选的属性。因此,在联合类型 Circle |Rectangle 中,area 属性也是可选属性。

索引签名

索引签名包含两种,即字符串索引签名和数值索引签名。在联合类型中,这两种索引签名具有相似的行为。

如果联合类型中每个成员都包含字符串索引签名,那么该联合类型也拥有了字符串索引签名,字符串索引签名中的索引值类型为每个成员类型中索引值类型的联合类型;否则,该联合类型没有字符串索引签名。例如,下例中的联合类型 T 相当于接口类型 T0T1

interface T0 {
    [prop: string]: number;
}

interface T1 {
    [prop: string]: bigint;
}

type T = T0 | T1;

interface T0T1 {
    [prop: string]: number | bigint;
}

如果联合类型中每个成员都包含数值索引签名,那么该联合类型也拥有了数值索引签名,数值索引签名中的索引值类型为每个成员类型中索引值类型的联合类型;否则,该联合类型没有数值索引签名。例如,下例中的联合类型 T 相当于接口类型 T0T1

interface T0 {
    [prop: number]: number;
}

interface T1 {
    [prop: number]: bigint;
}

type T = T0 | T1;

interface T0T1 {
    [prop: number]: number | bigint;
}

调用签名与构造签名

如果联合类型中每个成员类型都包含相同参数列表的调用签名,那么联合类型也拥有了该调用签名,其返回值类型为每个成员类型中调用签名返回值类型的联合类型;否则,该联合类型没有调用签名。例如,下例中的联合类型 T 相当于接口类型 T0T1

interface T0 {
    (name: string): number;
}

interface T1 {
    (name: string): bigint;
}

type T = T0 | T1;

interface T0T1 {
    (name: string): number | bigint;
}

同理,如果联合类型中每个成员都包含相同参数列表的构造签名,那么该联合类型也拥有了构造签名,其返回值类型为每个成员类型中构造签名返回值类型的联合类型;否则,该联合类型没有构造签名。例如,下例中的联合类型 T 相当于接口类型 T0T1

interface T0 {
    new (name: string): Date;
}

interface T1 {
    new (name: string): Error;
}

type T = T0 | T1;

interface T0T1 {
    new (name: string): Date | Error;
}