TypeScript中的联合类型

257 阅读4分钟

fd26-hwsffza7768851.jpg

什么是联合类型?

联合类型就是并集,从数学的角度来看:集合 {1, 2, 3} 和 {2, 3, 4} 的并集是 {1, 2, 3, 4}, TS的并集也是一样的,如下图所示:

image.png

上图中的 类型C1 就是 A1B1的并集,写做 A1 | B1,所以 C1 可以是 number也可以是string, C2也是如此,它是A2B2的并集,所以它的属性也可以是只有name 或者 age

如何使用联合类型

示例:

// 这个函数里面的参数a是一个联合类型
const f1 = (a: number | string) => {
    // 此时怎么使用参数 a 呢?
    // 既不能把 a 当作number (调用 toFixed 方法会报错) 
    // 也不能把 a 当作 string
    // a只能调用 number 和 string 的交集
}
  • 上面例子中的参数 a 可以是 number 也可以是 string, TS无法判断,它只能调用 number 和 string 类型的交集, 如 a.toString, 除了交集其他的不行。 所以必须判断一下它的类型是什么,这个就叫做类型收窄

使用 typeof 区分

const f1 = (a: number | string) => {
    if (typeof a === 'number') {
        a.toFixed(2)
    } else if (typeof a === 'string') {
        parseFloat(a).toFixed(2)
    } else {
        throw new Error('Never do this')
    }
}
  • 这种区分也叫类型收窄,包括下面的那些区分方式也是
  • 用typeof来判断也是有缺陷的,因为typeof只能判断简单类型,那么其他类型它就判断不了

如下图: image.png

image.png

使用 instanceof 来区分类型

const f1 = (a: Array<Date> | Date) => {
    if (a instanceof Date) {
        a.toISOString()
    } else if (a instanceof Array) {
        a[0].toISOString()
    } else {
        throw new Error('Never do this')
    }
}
  • instanceof 的局限性

    • 不支持 string, number, boolean等基本类型
    • 不支持 TS 独有的类型
  • 问:下面的例子要用什么来区分?

type Person = {
    name: string
}
const f1 = (a: Person | Person[]) {
    if (a intanceof Person) {
        // 用这个会报错:“Person”仅指类型,但在此处用作值。
    } else {
        throw new Error('Never do this')
    }
}

使用 in 来收窄类型

  • 只适用于部分对象,缺陷是很多对象它的属性可能是一样的
type Person = {
    name: string
}
const f1 = (a: Person | Person[]) {
    if ('name' in a) {
        a // Person
    } else {
        a // Person[] 
    }
}
  • 还有很多的方法可以来判断类型,但是,都有一些缺陷和局限性。上面的说的所有类型收窄都是通过JavaScript来实现的,

  • 那么有没有别的区分类型的万全之法呢?

类型谓词 / 类型判断 is

例子如下:

type Rect = {
  height: number
  width: number
}
type Circle = {
  center: [number, number]
  radius: number
}

const f1 = (a: Rect | Circle) => {
  if (isRect(a)) {
      // 鼠标移到a上面,ts会告诉你a的类型是Rect
      a
  }
  if (isCircle(a)) {
      a
  }
}

// 这里的 x is Rect 是 ts 语法,调用这个函数,如果通过,它就会告诉ts,类型是什么
function isRect(x: Rect | Circle): x is Rect {
  return 'height' in x && 'width' in x
  // 这里面的判断随意,可以是上面的 typeof, instanceof, in 等等,能判断就行
}

function isCircle(x: Rect | Circle): x is Circle {
    return 'center' in x && 'radius' in x
}

  • is的优点是:

    • 支持所有的TS类型
  • is的缺点是:

    • 麻烦

那有没有更简单的区分类型的方法呢?

可辨别联合

  • 我们可以在每个TS类型上加一个共同的属性,kind(随便起,xxx也行),然后判断的时候用kind判断

例子如下:

type Rect = {
  kind: 'rect'
  height: number
  width: number
}
type Circle = {
  kind: 'circle'
  center: [number, number]
  radius: number
}

const f1 = (a: Rect | Circle) => {
    if (a.kind === 'rect') {
        a
        // 类型是Rect
    } else if (a.kind === 'circle') {
        a
        // 类型是Cirle
    }
}

问: 那如果联合类型中有简单类型呢?

type Rect = {
  kind: 'rect'
  height: number
  width: number
}
type Circle = {
  kind: 'circle'
  center: [number, number]
  radius: number
}

const f1 = (a: Rect | Circle | string | number) => {
    if (typeof a === 'string') {
    
    } else if (typeof a === 'number') {
    
    } else if (a.kind === 'rect') {
        a
        // 类型是Rect
    } else if (a.kind === 'circle') {
        a
        // 类型是Cirle
    }
}
  • 优点

    让复杂类型的收窄变成简单类型的对比

  • 使用可辩别联合的要求

    如 T 联合了A,B,C,D : T = A | B | C | D

    • 要求A,B,C,D 有相同的属性,可以是kind或者其他
    • kind的类型必须是简单类型
    • 各类型中的kind可区分

    则称 T 为可辩别联合

    总结: 要有 同名、可辩别的简单类型的 key

思考

any 是所有类型的联合类型吗? 为什么?

  • 这里的所有类型除了 never / unknown / void
const f1 = (a: any) => {
    a
    // ?? 
}

我认为:any不是所有类型的联合类型。为什么呢?

我们上面说过,联合类型的调用只能使用它们之间的交集,需要类型收窄了才能使用。而any 呢,它什么都可以调用,所有我认为它不是所有类型的联合类型

const f1 = (a: any) => {
    a.push('b')
    a.splice(0)
    a.toFixed(2)
}

// TS不会报错

什么是所有类型的联合类型呢??

  • 我觉得 unknown 是所有类型的联合类型
  • 正如上面说的:联合类型的调用只能使用它们之间的交集,需要类型收窄了才能使用。
  • unknown 不知道它是什么类型,只要你给它判断一个类型,它就能使用这个类型

例子如下:

const f1 = (a: unknown) => {
    if (typeof a === 'string') {
        a
        // a的类型是 string
    } else if (a instanceof Date) {
        a
        // a 的类型是 Date
    }
    
}