联合类型

  1. interface Bird {
  2. fly(): void;
  3. layEggs(): void;
  4. }
  5. interface Fish {
  6. swim(): void;
  7. layEggs(): void;
  8. }
  9. const getPet: () => Bird | Fish = () => {
  10. return {
  11. // ...
  12. } as Bird | Fish;
  13. };
  14. const Pet = getPet();
  15. Pet.layEggs(); // ok
  16. Pet.fly(); // ts(2339) 'Fish' 没有 'fly' 属性; 'Bird | Fish' 没有 'fly' 属性

使用基于 in 操作符判断的类型守卫

  1. if (typeof Pet.fly === 'function') { // ts(2339)
  2. Pet.fly(); // ts(2339)
  3. }
  4. if ('fly' in Pet) {
  5. Pet.fly(); // ok
  6. }

交叉类型

  1. {
  2. type Useless = string & number;
  3. }

很显然,如果我们仅仅把原始类型、字面量类型、函数类型等原子类型合并成交叉类型,是没有任何用处的,因为任何类型都不能满足同时属于多种原子类型,比如既是 string 类型又是 number 类型。因此,在上述的代码中,类型别名 Useless 的类型就是个 never。

合并接口类型

  • 不同名属性,可以理解为求并集。

    1. type IntersectionType = { id: number; name: string } & { age: number };
    2. const mixed: IntersectionType = {
    3. id: 1,
    4. name: "name",
    5. age: 18,
    6. };
  • 如果同名属性的类型不兼容,属性交叉类型为never

    1. type IntersectionTypeConfict = { id: number; name: string; }
    2. & { age: number; name: number; };
    3. const mixedConflict: IntersectionTypeConfict = {
    4. id: 1,
    5. name: 2, // ts(2322) 错误,'number' 类型不能赋给 'never' 类型
    6. age: 2
    7. };
  • 如果同名属性的类型兼容,合并后 name 属性的类型就是两者中的子类型。####

    1. type IntersectionTypeConfict = { id: number; name: 2; }
    2. & { age: number; name: number; };
    3. let mixedConflict: IntersectionTypeConfict = {
    4. id: 1,
    5. name: 2, // ok
    6. age: 2
    7. };
    8. mixedConflict = {
    9. id: 1,
    10. name: 22, // '22' 类型不能赋给 '2' 类型
    11. age: 2
    12. };

    合并联合类型

    我们也可以将合并联合类型理解为求交集。

    1. type UnionA = 'px' | 'em' | 'rem' | '%';
    2. type UnionB = 'vh' | 'em' | 'rem' | 'pt';
    3. type IntersectionUnion = UnionA & UnionB;
    4. const intersectionA: IntersectionUnion = 'em'; // ok
    5. const intersectionB: IntersectionUnion = 'rem'; // ok
    6. const intersectionC: IntersectionUnion = 'px'; // ts(2322)
    7. const intersectionD: IntersectionUnion = 'pt'; // ts(2322)

    既然是求交集,如果多个联合类型中没有相同的类型成员,交叉出来的类型自然就是 never 了,如下代码所示:

    1. type UnionC = 'em' | 'rem';
    2. type UnionD = 'px' | 'pt';
    3. type IntersectionUnionE = UnionC & UnionD;
    4. const intersectionE: IntersectionUnionE = 'any' as any; // ts(2322) 不能赋予 'never' 类型

    联合、交叉组合

    联合、交叉类型本身就可以直接组合使用,这就涉及 |、& 操作符的优先级问题。实际上,联合、交叉运算符不仅在行为上表现一致,还在运算的优先级和 JavaScript 的逻辑或 ||、逻辑与 && 运算符上表现一致 。

联合操作符 | 的优先级低于交叉操作符 &,同样,我们可以通过使用小括弧 () 来调整操作符的优先级。

  1. type UnionIntersectionA = { id: number; } & { name: string; } | { id: string; } & { name: number; }; // 交叉操作符优先级高于联合操作符
  2. type UnionIntersectionB = ('px' | 'em' | 'rem' | '%') | ('vh' | 'em' | 'rem' | 'pt'); // 调整优先级
  1. type UnionIntersectionC = ({ id: number; } & { name: string; } | { id: string; }) & { name: number; };
  2. type UnionIntersectionD = { id: number; } & { name: string; } & { name: number; } | { id: string; } & { name: number; }; // 满足分配率
  3. type UnionIntersectionE = ({ id: string; } | { id: number; } & { name: string; }) & { name: number; }; // 满足交换律

在上述代码中,第 2 行是在第 1 行的基础上进行展开,说明 & 满足分配率;第 3 行则是在第 1 行的基础上调整了成员的顺序,说明 | 操作满足交换律。

类型缩减

  1. type URStr = 'string' | string; // 类型是 string
  2. type URNum = 2 | number; // 类型是 number
  3. type URBoolen = true | boolean; // 类型是 boolean
  4. enum EnumUR {
  5. ONE,
  6. TWO
  7. }
  8. type URE = EnumUR.ONE | EnumUR; // 类型是 EnumUR

TypeScript 对这样的场景做了缩减,它把字面量类型、枚举成员类型缩减掉,只保留原始类型、枚举类型等父类型,这是合理的“优化”。

可是这个缩减,却极大地削弱了 IDE 自动提示的能力,如下代码所示:

  1. type BorderColor = 'black' | 'red' | 'green' | 'yellow' | 'blue' | string; // 类型缩减成 string

在上述代码中,我们希望 IDE 能自动提示显示注解的字符串字面量,但是因为类型被缩减成 string,所有的字符串字面量 black、red 等都无法自动提示出来了。

不要慌,TypeScript 官方其实还提供了一个黑魔法,它可以让类型缩减被控制。如下代码所示,我们只需要给父类型添加“& {}”即可。

  1. type BorderColor = 'black' | 'red' | 'green' | 'yellow' | 'blue' | string & {}; // 字面类型都被保留

当联合类型的成员是接口类型,如果满足其中一个接口的属性是另外一个接口属性的子集,这个属性也会类型缩减,如下代码所示:

  1. type UnionInterce =
  2. {
  3. age: '1';
  4. } |
  5. ({
  6. age: '1' | '2';
  7. [key: string]: string;
  8. });
  9. let a: UnionInterce = {
  10. age: '1',
  11. 'fs': 'f',
  12. 33: 333 // 不能将类型“number”分配给类型“string”。ts(2322)
  13. }

如何定义如下所示 age 属性是数字类型,而其他不确定的属性是字符串类型的数据结构的对象?

  1. {
  2. age: 1, // 数字类型
  3. anyProperty: 'str', // 其他不确定的属性都是字符串类型
  4. ...
  5. }

这个问题的核心在于找到一个既是 number 的子类型,这样 age 类型缩减之后的类型就是 number;同时也是 string 的子类型,这样才能满足属性和 string 索引类型的约束关系。

  1. type UnionInterce = {
  2. age: number,
  3. } | {
  4. age: never,
  5. [key: string]: string
  6. }
  7. let a: UnionInterce = {
  8. age: 1,
  9. name: 'zhl'
  10. }