ts 进阶用法 --- 2. 联合类型与交叉类型: ts类型的编程能力

4,442 阅读4分钟

1. union 联合类型

"|" 类型"或" 声明联合类型

类比 js中 || 或符号 类型声明 或 用 "|" 符号

联合类型还可以把接口类型联合起来 表示更复杂的类型结构

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

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

const getPet: () => Bird | Fish = () => {
  return {
   // ...
  } as Bird | Fish;
  
  // 此处 return { } 中 如果不类型断言将返回值断言为 Bird | Fish 会报类型错误
};

const Pet = getPet();
Pet.layEggs(); // ok
Pet.fly(); // ts(2339) 'Fish' 没有 'fly' 属性; 'Bird | Fish' 没有 'fly' 属性

//扩展 此时 Pet.fly 会报类型错误  因为 Fish类型中没有fly属性  我们这个时候可以用类型守卫来区分不同成员类型
使用基于 in 操作符判断的类型守卫
if ('fly' in Pet) {
  Pet.fly(); // ok
}

2. intersection 交叉类型

出了逻辑或,和js一样 类型也存在逻辑与 "&" 表示交叉类型, 很显然如果我们仅仅把一些基础类型 执行&操作 其类型就会声明出一些"不存在"类型 因为任何类型都不能满足同时属于多种原子类型,比如既是 string 类型又是 number 类型。 因此,这些"不存在"的类型就是个 never。

交叉类型还可以把接口交叉 理解成接口类型求并集所得到的类型

  1. 合并的多个接口类型 可以将多个接口类型求并集得到的结果
  type IntersectionTypeConfict = { id: number; name: string; } & { age: number; sex: string; };
  const mixedConflict: IntersectionTypeConfict = {
    id: 1,
    sex: '男',
    age: 2,
    name: '123'
  };
  1. 合并的多个接口类型中存在同名属性会是什么效果?

可以分为两种情况:

  • 同名属性兼容: 兼容的同名属性 合并后会是两者类型的子类型 (同 type name = string & '2' // '2' 类型 )
  • 同名属性不兼容: 不兼容的合并后会得到 never类型 (同 type name = string & number //never类型)

合并联合类型 (将联合类型交叉 === 联合类型求交集)

合并联合类型后 生成类型需要同时满足不同的联合类型限制 也就是提取了所有联合类型的相同类型成员 可以将合并联合类型理解为求两个联合类型的交集

 type UnionA = 'a' | 'b' | 'c' | 'd';
 type UnionB = 'c' | 'd' | 'e' | 'f';
 type UnionC = UnionA & UnionB  // type UnionC = 'c' | 'd'

3. 联合、交叉类型本身就可以直接组合使用

联合、交叉运算符不仅在行为上表现一致,还在运算的优先级和 JavaScript 的逻辑或 ||、逻辑与 && 运算符上表现一致
联合操作符 | 的优先级低于交叉操作符 &,同样,我们可以通过使用小括弧 () 来调整操作符的优先级 进而,我们也可以把分配率、交换律等基本规则引入类型组合中,然后优化出更简洁、清晰的类型

4. 类型缩减

  1. 如果将 string 原始类型和“string字面量类型”组合成联合类型会是什么效果?效果就是类型缩减成 string 了

type a = string | 'string' => type a = string

  1. 同样对于 number boolean 枚举类型 也有一样的类型缩减逻辑
 type URStr = 'string' | string; // 类型是 string
  type URNum = 2 | number; // 类型是 number
  type URBoolen = true | boolean; // 类型是 boolean
  enum EnumUR {
    ONE,
    TWO
  }
  type URE = EnumUR.ONE | EnumUR; // 类型是 EnumUR

ts把字面量类型、枚举成员类型缩减掉,只保留原始类型、枚举类型等父类型,这是合理的“优化”。 但是对与IDE中的提示, 这个缩减很大地削弱了自动提示的能力 如下代码 BorderColor 只会提示成 string类型 所有的字符串字面量 black、red 等都无法自动提示出来了 ts官方提供一个黑魔法 可以让类型缩减被控制 只需要 在父类型后面添加 &{} 即可

  type BorderColor = 'black' | 'red' | 'green' | 'yellow' | 'blue' | string; // 类型缩减成 string
    type BorderColor2 = 'black' | 'red' | 'green' | 'yellow' | 'blue' | string & {}; // 字面类型都被保留
  1. 同样当联合类型的成员是接口类型 如果满足其中一个接口的属性是另外一个接口属性的子集,这个属性也会类型缩减
type UnionInterce = { age: "1" } | { age: "1" | "2"; [key: string]: string };

思考: 如何定义如下所示 age 属性是数字类型,而其他不确定的属性是字符串类型的数据结构的对象(利用类型缩减)?

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