类型运算

运算律

改变成员类型的顺序不影响联合类型的结果类型。

  1. type T0 = string | number;
  2. type T1 = number | string;

对部分类型成员使用分组运算符不影响联合类型的结果类型。

  1. type T0 = (boolean | string) | number;
  2. type T1 = boolean | (string | number);

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

  1. type T0 = boolean | true | false;
  2. // 所以T0等同于 T1
  3. type T1 = boolean;

优先级

&的优先级高于|

  1. A & B | C & D
  2. // 该类型等同于如下类型:
  3. (A & B) | (C & D)

分配律

  1. A & (B | C)
  2. // 等同于
  3. (A & B) | (A & C)

一个稍微复杂的类型等式。

  1. (A | B) & (C | D) A & C | A & D | B & C | B & D
  1. T = (string | 0) & (number | 'a');
  2. T = (string & number) | (string & 'a') | (0 & number) | (0 & 'a');
  3. T = never | 'a' | 0 | never;
  4. T = 'a' | 0;
  1. function extend<T extends object, U extends object>(first: T, second: U): T & U {
  2. const result = <T & U>{};
  3. for (let id in first) {
  4. (<T>result)[id] = first[id];
  5. }
  6. for (let id in second) {
  7. if (!result.hasOwnProperty(id)) {
  8. (<U>result)[id] = second[id];
  9. }
  10. }
  11. return result;
  12. }
  13. const x = extend({ a: 'hello' }, { b: 42 });

never 类型

never 可以视为空集。

  1. type NeverIntersection = never & string; // Type: never
  2. type NeverUnion = never | string; // Type: string

很适合在交叉类型中用作过滤。

  1. type OnlyStrings<T> = T extends string ? T : never;
  2. type RedOrBlue = OnlyStrings<"red" | "blue" | 0 | false>;
  3. // Equivalent to: "red" | "blue"

范例:https://www.typescriptlang.org/play#example/conditional-types

unknown 类型

在联合类型中,unknown吸收所有类型。这意味着如果任何组成类型是unknown,则联合类型的计算结果为unknown。

  1. // In an intersection everything absorbs unknown
  2. type T00 = unknown & null; // null
  3. type T01 = unknown & undefined; // undefined
  4. type T02 = unknown & null & undefined; // null & undefined (which becomes never)
  5. type T03 = unknown & string; // string
  6. type T04 = unknown & string[]; // string[]
  7. type T05 = unknown & unknown; // unknown
  8. type T06 = unknown & any; // any
  9. // In a union an unknown absorbs everything
  10. type T10 = unknown | null; // unknown
  11. type T11 = unknown | undefined; // unknown
  12. type T12 = unknown | null | undefined; // unknown
  13. type T13 = unknown | string; // unknown
  14. type T14 = unknown | string[]; // unknown
  15. type T15 = unknown | unknown; // unknown
  16. type T16 = unknown | any; // any
  17. // Type variable and unknown in union and intersection
  18. type T20<T> = T & {}; // T & {}
  19. type T21<T> = T | {}; // T | {}
  20. type T22<T> = T & unknown; // T
  21. type T23<T> = T | unknown; // unknown
  22. // unknown in conditional types
  23. type T30<T> = unknown extends T ? true : false; // Deferred
  24. type T31<T> = T extends unknown ? true : false; // Deferred (so it distributes)
  25. type T32<T> = never extends T ? true : false; // true
  26. type T33<T> = T extends never ? true : false; // Deferred
  1. type UnionType1 = unknown | null; // unknown
  2. type UnionType2 = unknown | undefined; // unknown
  3. type UnionType3 = unknown | string; // unknown
  4. type UnionType4 = unknown | number[]; // unknown

该规则的一个例外是any。如果至少有一种构成类型是any,则联合类型的计算结果为any:

  1. type UnionType5 = unknown | any; // any

在交叉类型中,每种类型都吸收unknown. 这意味着与任何类型相交unknown不会改变结果类型:

  1. type IntersectionType1 = unknown & null; // null
  2. type IntersectionType2 = unknown & undefined; // undefined
  3. type IntersectionType3 = unknown & string; // string
  4. type IntersectionType4 = unknown & number[]; // number[]
  5. type IntersectionType5 = unknown & any; // any

除非使用as断言,首先缩小类型unknown类型的范围,然后才可以用于其他类型。

  1. const value: unknown = "Hello World";
  2. const someString: string = value as string;
  3. const otherString = someString.toUpperCase(); // "HELLO WORLD"

联合类型

如果类型是多个值的联合,甚至可以产生插值的效果。

  1. type EmailLocaleIDs = "welcome_email" | "email_heading";
  2. type FooterLocaleIDs = "footer_title" | "footer_sendoff";
  3. // 等同于 type AllLocaleIDs = "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"
  4. type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;

交叉类型

  1. type Brightness = "dark" | "light";
  2. type Color = "blue" | "red";
  3. type BrightnessAndColor = `${Brightness}-${Color}`;
  4. // Equivalent to: "dark-red" | "light-red" | "dark-blue" | "light-blue"

如果交叉类型中存在多个相同的成员类型,那么相同的成员类型将被合并为单一成员类型。

  1. type T0 = boolean;
  2. type T1 = boolean & boolean;
  3. type T2 = boolean & boolean & boolean;

上面示例中,T0、T1和T2都表示同一种类型boolean。

改变成员类型的顺序不影响交叉类型的结果类型。

  1. interface Clickable {
  2. click(): void;
  3. }
  4. interface Focusable {
  5. focus(): void;
  6. }
  7. type T0 = Clickable & Focusable;
  8. type T1 = Focusable & Clickable;

注意,当交叉类型涉及调用签名重载或构造签名重载时便失去了“加法交换律”的性质。因为交叉类型中成员类型的顺序将决定重载签名的顺序,进而将影响重载签名的解析顺序。

  1. interface Clickable {
  2. register(x: any): void;
  3. }
  4. interface Focusable {
  5. register(x: string): boolean;
  6. }
  7. type ClickableAndFocusable = Clickable & Focusable;
  8. type FocusableAndFocusable = Focusable & Clickable;
  9. function foo(
  10. clickFocus: ClickableAndFocusable,
  11. focusClick: FocusableAndFocusable
  12. ) {
  13. let a: void = clickFocus.register('foo');
  14. let b: boolean = focusClick.register('foo');
  15. }

此例第8行和第9行使用不同的成员类型顺序定义了两个交叉类型。第15行,调用“register()”方法的返回值类型为void,说明在ClickableAndFocusable类型中,Clickable接口中定义的“register()”方法具有更高的优先级。第16行,调用“register()”方法的返回值类型为boolean,说明FocusableAndFocusable类型中Focusable接口中定义的“register()”方法具有更高的优先级。此例也说明了调用签名重载的顺序与交叉类型中成员类型的定义顺序是一致的。

对部分类型成员使用分组运算符不影响交叉类型的结果类型。

  1. interface Clickable {
  2. click(): void;
  3. }
  4. interface Focusable {
  5. focus(): void;
  6. }
  7. interface Scrollable {
  8. scroll(): void;
  9. }
  10. type T0 = (Clickable & Focusable) & Scrollable;
  11. type T1 = Clickable & (Focusable & Scrollable);

上面示例的T0和T1类型是同一种类型。

  1. type Combined = { a: number } & { b: string };
  2. type Conflicting = { a: number } & { a: string };

只要交叉类型I中任意一个成员类型包含了属性签名M,那么交叉类型I也包含属性签名M。

  1. interface A {
  2. a: boolean;
  3. }
  4. interface B {
  5. b: string;
  6. }
  7. // 交叉类型如下
  8. {
  9. a: boolean;
  10. b: string;
  11. }

若交叉类型的属性签名M在所有成员类型中都是可选属性,那么该属性签名在交叉类型中也是可选属性。否则,属性签名M是一个必选属性。

  1. interface A {
  2. x: boolean;
  3. y?: string;
  4. }
  5. interface B {
  6. x?: boolean;
  7. y?: string;
  8. }
  9. // 交叉类型如下
  10. {
  11. x: boolean;
  12. y?: string;
  13. }