本章我们来学习联合类型以及类型别名。

联合类型

基础类型一文中我们提到过,将变量定义为字符串类型后是不能再赋值为其他类型的,那如果我们需要这个变量既可以是字符串也可以是数值该怎么办?此时我们就可以使用联合类型来实现需求。

联合类型(Union Types)表示取值可以为多种类型中的一种,除此以外的类型则会报错。举个例子:

let field: string | number
field = 'first' // 成功
console.log(field.length) // 5
field = 1 // 成功
console.log(field.length) // error TS2339: Property 'length' does not exist on type 'number'
field = false // error TS2322: Type 'boolean' is not assignable to type 'string | number'

联合类型使用 | 分隔每个类型,上面例子中 field 既可以是 string 也可以是 number 类型。当它是 string 时可以使用 string 的属性或方法,比如属性 length ;当它是 number 时,因为 number 没有 length 属性,所以会编译报错。

上面的例子是通过 类型推论 得到变量准确的类型,从而可以使用相应类型的属性或方法。当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里 共有的 属性或方法。举个例子:

function getLength(something: string | number): number {
  return something.length; // error TS2339: Property 'length' does not exist on type 'string | number'.  Property 'length' does not exist on type 'number'
}

上面例子中,因为 length 不是 stringnumber 共有的属性,所以会报错。访问 stringnumber 的共有属性是没问题的,举个例子:

function getLength(something: string | number): string {
  return something.toString();
}

联合类型不仅可以是基础类型,也可以是接口或者类,举个例子:

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

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

function getSmallPet(): Fish | Bird {
  let bird: Bird = {
    fly() {},
    layEggs() {}
  }
  return bird
}

let pet = getSmallPet();
pet.layEggs(); // 成功
pet.swim(); // error TS2339: Property 'swim' does not exist on type 'Bird | Fish'.Property 'swim' does not exist on type 'Bird'
pet.fly(); // error TS2339: Property 'fly' does not exist on type 'Bird | Fish'.Property 'fly' does not exist on type 'Fish'

上面例子中联合类型为 FishBird ,只有它们共有的方法 layEggs 可以访问,因此 swimfly 都会编译报错。

看了上面的介绍,你可能会觉得 any 也能实现同样的效果,就代码方面而言确实是这样的,将联合类型改为 any 是不会编译报错的,但是与此同时你也失去了 TypeScript 最大的功能。在基础类型一文中有介绍 any 的使用场景,比如在编译阶段不清楚类型的情况下可以使用。

类型别名

类型别名用来给一个类型起个新名字。起别名的方法很简单,使用type关键字。

type 别名 = 类型(原始类型、联合类型、元组…)

举个例子:

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
  if (typeof n === 'string') {
    return n;
  } else {
    return n();
  }
}

起别名不会新建一个类型,它是创建了一个新名字来引用那个类型。比如上面例子,Name 和 string 其实是一样的类型。通常来说给 原始类型 起别名没有特别用处,更多的用于 联合类型 ,举个例子:

type Field = string | number
let field: Field
field = 'first'
field = 1

类型别名有时和接口很像,但是可以作用于 原始值联合类型元组以及其它任何你需要手写的类型。类型别名与接口刚接触时可能会混淆,举个例子:

type Field = {
  num: number;
  str: string;
}

interface inter {
  num: number;
  str: string;
}

上面例子中类型别名和接口使用上比较类似,但是还是有很明显的区别的,具体区别我会放在下一篇接口篇中细谈。

类型别名是可以使用泛型 的,关于 泛型 的内容会在之后介绍,此处简单讲下在类型别名中的使用。举个例子:

type Container<T> = {
  value: T
}

let container: Container<string> = {
  value: 'str'
}

此时 value 的类型是根据传递的类型参数决定的。

我们也可以使用类型别名来在属性里引用自己:

type Container<T> = {
  value: T;
  children?: Container<T>
}

let container: Container<string> = {
  value: 'str',
  children: {
    value: 'str2'
  }
}