08 联合类型与交叉类型

699 阅读4分钟

前言: 基础类型,字面量类型,函数类型以及接口类型这些都是单一的,原子的类型元素。 实际编程中, 通常我们要处理的都是复杂的场景,我们可以通过组合,交叉这些单一地、原子类型来构造更复杂的类型。这种组合和交叉在TypeScript中有专业的术语:联合类型(Union Types),交叉类型(Intersection Types)

联合类型

所谓联合类型,即表示变量,参数的类型不是单一原子类型,而可能是多种不同的类型的组合, 通过|表示。

function unionTypes(size: number | string) {}

【注意】: 使用联合类型之前,我们可以会使用any或unkown类型来表示参数的类型(推荐使用unknown。)。

使用类型别名定义联合类型

type ModernUnit = 'vh' | 'vw'
type Unit = 'px' | 'em' | 'rem'
type MessedUp = NodernUnit | Unit; // 将ModernUnit和Unit联合起来

将string类型与string字面量类型组合成一个联合类型,会是什么效果呢?

答案是类型为缩减为string的类型,即基本类型。

使用接口类型组成联合类型

interface Bird {
    fly(): void;
    layEggs() : void;
}

interface Fish {
    swim(): void;
    layEggs(): void;
}

const getPet: () => Bird | Fish = () => {
    return {} as Bird | Fish
}

const pet = getPet()
pet.layEggs(); //ok
pet.fly();// ts2339

如果需要使用fly方法,需要使用类型守卫来收缩类型至Bird类型

需要注意的是, 直接使用pet.fly时,就会提示ts2339(Fish没有fly属性, Fish | Bird没有fly属性), 可以使用in,as类型守卫

// ts 2339
if(pet.fly){}
if(typeof pet.fly === 'function'){}

// ok
if('fly' in pet){}
(pet as Bird).fly()

交叉类型

所谓交叉类型(intersection type),它可以把多个类型合并成一个类型,合并后的类型将拥有所有成员类型的特性。使用 &表示。

// 合并原始类型没有意义, 因为没有满足同时是string和number的类型
type Useless = string & number;

合并接口类型

将多个接口合并成一个类型,从而实现同等接口继承的效果。

type IntersectionType = {id: number, name: string;} & {age: number;}

问题,如果和宾得的多个接口类型存在同名属性会是什么效果?

分两种情况:

  1. 两个类型不兼容。 比如number与string交叉后是never类型,不能给这个属性赋值了(ts2322)
  2. 两个类型兼容。 比如number和number的子类型2, 合并后为子类型 type intersectionTypeConflict = {name: 2} & {name: number} // 交叉后 {name: 2}

合并联合类型

多个联合类型交叉时,结果是提取所有联合类型的相同类型成员, 即多个联合类型交叉时是求交集。如何没有相同的类型成员(交集为空),则交叉后的类型为never

type UnionA = 'px' | 'em' | 'rem' | '%';
type UnionB = 'vh' | 'vw' | 'rem' | 'pt';
type IntersectionUnion = Union & Union; // rem

type UnionC = 'wpx';
type IntersectionUnionB = UnionA & UnionC; // never

联合,交叉组合

当联合,交叉类型组合时,联合操作符|优先级低于交叉操作符&(与javascript的逻辑或||,逻辑与&&表现一致)。可以使用括号()调整优先级。

type IntersectionA = {id: number} & {name: string} | {id: string} & {name: number}
// 等价于{id:number,name: string} | {id: string, name: number}

type UnionIntersectionB = ('px'| 'em'| 'rem'| '%') & ('vh'| 'vw'| 'em')

类型缩减

如果将基础类型,基础类型的子类型联合,得到的类型为基础类型。 枚举也联合成枚举基础类型。

type URStr = 'str' | string; // string 类型
type URNum = 12 | number; // number类型
type URBoolen = true | boolean; // boolean类型

type UREnum = {
    ONE,
    TWO
}
type URE = UREnum.ONE | UREnum; // UREnum类型

缩减掉字面量类型,枚举成员类型,只保留原始类型,枚举类型等父类型。 这是合理的。

但是如: type BorderColor = 'black' | 'red' | 'yelllow' | 'green' | string, 类型联合后,BorderColor联合成string基础类型,VS Code就不能给出前面定义好的类型的提示。 不过ts提供了 黑魔法--只需给父类型添加&{} 即可。

image.png

联合类型的成员是接口

如果满足其中一个接口的属性时另外一个接口属性的子类型, 这个属性也会类型缩减。

type UnionIntersection =
    {
        age: '1'
    }
        | 
    {
        age: '1' | '2',
        [key: string]: string;
    }

因为'1''1' | '2'的子类型, 所以类型缩减为'1' | '2'

应用: {age: 1, otherProperty: string},想要指定age为number类型,可以和一个number的子类型进行联合。

type UnionInterce = 
    {
        age: number
    }
        |
    {
        age: never;
        [key: string]: string
    }     

因为never是所有类型的子类型。所以numbernever缩减后后为number类型。

The End!

传送门:

总结: any 既是所有类型的父类型, 也是所有类型的子类型 unknown 是所有类型的父类型 void 无任何类型 never 是所有类型的子类型