除了几种基本类型,TS 还有一些高级类型,其中包括交叉类型和联合类型这两种。

交叉类型(intersection types)

交叉类型可以让我们把现有的类型组合在一起得到一个新的类型,从而同时拥有它们的全部属性,表示方法是:A & B

interface IPerson {
  name: string;
  age: number;
}
interface IStudent {
  grade: number;
}
const getBio = (user: IPerson & IStudent) => {
  return `His name is ${user.name}, I am ${user.age} years old and a student of Grade ${user.grade}.`
}
getBio({name: ‘Joi’, age: 12, grade: 6})

跟集合里的相交不一样,TS 的交叉类型并不是指每个类型的交集,& 的意思理解成 andA & B 表示同时包含 A B 的结果,这里传进去的 user 必须同时拥有 name, age, grade 这三个属性,我们可以直接使用它而不需要判断是否存在该属性。

联合类型(union types)

联合类型应该是我们在 TypeScript 使用得最多的特性之一,它使用管道符 | 把多个类型连起来,表示它有可能是这些类型中的其中一个:

const greet = (name: string | null) => {
  if (!name) {
    return `Hello, guest.`
  } else {
    return `Hello, ${name}.`
  }
}

强调有可能是因为它跟集合中的不同,| 应该理解成 orA | B 表示 A 或 B 的结果,它只可能是其中一个,这也就意味着它的类型是不确定的。

interface ICat {
  run(): void
  meow(): void
}
interface IDog {
  run(): void
  bark(): void
}
class Cat implements ICat {
    run() { };
    meow() { };
}
const getAnimal = (): ICat | IDog {
    return new Cat();
}

const animal = getAnimal();
animal.run(); // ok
animal.meow(); // error

这里 ICatIDog 都拥有 run 方法,我们不确定返回的具体是什么类型,如果想要编译通过就需要使用类型断言:

// 类型断言
(animal as ICat).meow() // ok

// 判断是否存在该方法
if (‘meow’ in animal) {
  animal.meow()
} else {
  animal.bark();
}

看起来它们的叫法换过来更符合直觉一点,至于为什么叫这两个名字,官方 repo 有个专门讨论这个问题的 issue] ,根据维护者的说法,这应该是约定俗成的,并不是他们刻意起的,是符合类型理论和其它语言的习惯的。

基于联合类型的条件类型分发

条件类型指的是 T extends U ? X : Y ,在执行这条语句的时候,如果 T 是一个联合类型 A | B | C ,事实上它做的是: (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y) ,会在每个可能的类型分支上分别进行判断,最后得到的也是一个联合类型。基于这个特性,就可以做一些方便的操作:

// 移除属性值
type Diff<T, U> = T extends U ? never : T
type T1 = Diff<‘a’ | ‘b’ | ‘c’, ‘b’ | ‘d'> // ‘a’ | ‘c’