类型断言

当一个变量是联合类型的时候,我们能够确定的只有每个类型共有的方法,除非准确地知道该变量属于哪个类型。

const canvas = document.getElementById('#canvas') // HTMLElement | null
const ctx = canvas.getContext('2d') // 错误

在上述代码中, canvas 元素可能不存在于文档中,所以 TS 推断它的类型是 HTMLElement | null ,不管是 null 还是 HTMLElement 都没有 getContext 方法。当我们很确定自己定义了该元素,并且它还是一个 canvas 元素,就可以使用 类型断言 来指定它的类型,告诉编译器「我很清楚自己在做什么,这里不会出错」。 类型断言的写法是 <type>variable 或者 variable as type ,之前的代码可以写成这样:

const canvas = document.getElementById('#canvas') // HTMLElement | null
const ctx = (canvas as HTMLCanvasElement).getContext('2d') 
// 或者 (<HTMLCanvasElement>canvas).getContext(‘2d’)

类型断言可以让我们在联合类型中对不同的类型做不同的事情,但是有时候这样也显得繁琐,假设我们有几个图形,有不同的求面积的方法:

interface ICircle {
  radius: number
}
interface IRectangle {
  width: number;
  height: number;
}

const getArea = (shape: ICircle | IRectangle): number => {
  if ((<ICircle>shape).radius) {
    return Math.PI * ((<ICircle>shape).radius ** 2);
  } else {
    return (shape as IRectangle).width * (shape as IRectangle).height;
  }
}

这样我们都需要写多次断言语句,体验实在不是特别友好。

类型保护

在上面的代码中,不仅在 if 条件分支里面需要写断言,在 else 分支同样要进行断言。通过类型保护,TS 可以判断出不同条件分支的类型。

in

in 用来进行属性检测,它也可以用来做类型保护的判断:

const calcArea = (shape: ICircle | IRectangle): number => {
  if (‘radius’ in shape) {
    return Math.PI * (shape.radius ** 2);
  } else {
    return shape.width * shape.height;
  }
}

instanceof

在 JavaScript 中 instanceof 被用来判断构造函数的实例,所以在它的右边必须是一个构造函数(或者类), TypeScript 会根据构造函数的签名得到它的属性以及类型。

class Circle implements ICircle {
  constructor(public radius: number) {}
}

class Rectangle implements IRectangle {
  constructor(public width: number, public height: number) {}
}

const myArea = (shape: Circle | Rectangle): number => {
  if (shape instanceof Circle ) {
    return Math.PI * (shape.radius ** 2);
  } else {
    return shape.width * shape.height;
  }
}

typeof

typeof 用来做类型判断,当它用于类型保护的时候, TS 同样能够识别出当前类型,从而允许我们基于该类型做不同的操作。它有两种写法:typeof variable === ‘typename’typeof variable !== ‘typename’

const repeat = (x: number | string): number | string {
  if (typeof x === ‘number’) {
    return x * 2;
  } 
  return x + x + ''
}

需要注意的是这种用法只适用于原始类型,也即 typename 只能是 string number boolean symbol

自定义类型保护

在上面的几种类型保护中,instanceof 的右边必须是一个构造函数,而 typeof 只对原始类型的判断生效,对于一个普通的对象无法使用这两种方法进行类型保护,TS 提供另外一种叫类型预设 (type predicates) 的用法允许我们自定义类型保护,它的形式是在函数的返回值里面写成 param is Typeparam 必须是当前函数签名中的一个参数。

function isCircle(shape: ICircle | IRectangle): shape is ICircle {
  return (<ICircle>shape).radius !== undefined;
}

class Rectangle implements IRectangle {
  constructor(public width: number, public height: number) {}
}

const myArea = (shape: Circle | Rectangle): number => {
  if (isCircle(shape) ) {
    return Math.PI * (shape.radius ** 2);
  }
  return shape.width * shape.height;
}

TS 不仅知道在 if 分支是 ICircle 类型,也知道在另一个分支是 IRectangle 类型。

类型保护的各种用法都是会做运行时 (runtime) 检查的,可以确保我们代码使用不会出问题,而在编译时编译器也不会报错。