likes
comments
collection
share

Typescript: 类型保护之类型断言、类型谓词

作者站长头像
站长
· 阅读数 21

在使用 ts 过程中,对于新手最怕出现 ts 告警,其中很多是因为类型保护导致的,本文梳理了关于类型保护的几种解法,欢迎阅读。

类型保护

首先通过一个例子来了解什么是类型保护。

interface Bird {
  fly: boolean
  sing: () => {}
}

interface Dog {
  fly: boolean
  bark: () => {}
}

function trainAnimal(animal: Bird | Dog) {
  animal.sing() // 提示错误
}

当我访问参数 animal.sing 的时候,报如下错误:

Typescript: 类型保护之类型断言、类型谓词

这是因为ts并不知道 animal 到底有没有 sing 方法,因为它有可能是 Dog。

像这种通过联合类型定义的一个变量,当我们访问这个变量的时候,只会提示公共的属性和方法。上面那个例子,只有fly是两者共有的,所以可以访问animal.fly没有问题。

如果你直接访问animal.sing,就会报错,要避免这种报错,就需要确定它的类型,这个确定的过程就叫类型保护

那我们可以通过什么方式来做类型保护呢?

类型断言 as

对于上面的例子,我们可以使用类型断言进行类型保护,改造如下:

function trainAnimal(animal: Bird | Dog) {
  if ((animal as Bird).sing) {
    ;(animal as Bird).sing()
  } else if ((animal as Dog).bark) {
    ;(animal as Dog).bark()
  }
}

当访问animal时,首先利用as进行断言成某一个类型,然后判断这个类型是否存在某个属性,这样就可以放心访问animal.sing了。

再来看一个例子,sameValue 是 any 类型,但是我知道它其实是个字符串类型,那么也可以使用类型断言成字符串类型

let sameValue: any = 'this is a string'
// 推荐使用这种语法
let strLength: number = (sameValue as string).length 
// 还可以这么写,进行强制转换
let strLength: number = (<string>sameValue).length

除了类型断言外,还有其他的几种方式可以进行类型保护。

关键字 typeof instanceof in 类型保护

typeof

如果变量是基本类型而不是复杂类型,可以直接使用 typeof 来做类型保护,这样比类型断言简洁一些。

function add(first: string | number, second: string | number) {
  if (typeof first === 'string' || typeof second === 'string') {
      return `${first}${second}`
  } else {
      return first + second
  }
}

instanceof

instanceof 操作符是 JS 中的原生操作符,用来判断一个实例是不是某个构造函数创建的,或者是不是使用 es6 语法的某个类创建的。在 ts 中,使用 instanceof 操作符也可以达到类型保护的效果。

class Fish {
  swim() {
      console.log('swim')
  }
  eat() {}
}

class Mouse {
  miao() {
      console.log('miao')
  }
  eat(){}
}

function getRandPet () : Fish | Mouse {
  return Math.random() > 0.5 ? new Fish() : new Mouse() 
}

const petName = getRandPet()
if (petName instanceof Fish) {
  petName.swim()
} 
if (petName instanceof Mouse) {
  petName.miao()
}

通过petName instanceof FishpetName类型进行了保护,这样访问petName.swim就不会报错。

in

还是文章开头的那个例子,下面用 in 来进行类型保护:

function trainAnimal(animal: Bird | Dog) {
  if ('sing' in animal) {
    animal.sing()
  } else {
    animal.bark()
  }
}

可以看到这比上面所有的类型保护写法更加简洁。

以上三种类型保护,在开发过程中灵活使用就可以了。

类型谓词 is

下面我们来看一类特殊的类型保护:类型谓词 is。下面通过一个例子来看看什么是类型谓词。

下面的代码 ts 会直接报错,因为unknown类型上没有toUpperCase方法。

function upperCase(str: unknown) {
  // 类型unknown上不存在属性toUpperCase,所以报错
  str.toUpperCase()
}

那怎么修改呢?我们可以定义一个判断是否为字符串类型的函数isString

function isString(s: unknown): boolean {
  return typeof s === 'string'
}

function upperCase(str: unknown) {
  if (isString(str)) {
    str.toUpperCase() // 报错
  }
}

我们发现依然报错,我们虽然判断了参数strstring类型, 但参数str的类型还是unknown

Typescript: 类型保护之类型断言、类型谓词

也就是说这个条件判断并没有更加明确str的具体类型。

此时,可以在判断是否为string类型的函数返回值类型使用is关键词,即类型谓词。

// 判断参数是否为string类型, 返回布尔值
function isString(s:unknown):s is string{
  return typeof s === 'string'
}

// 判断参数是否为字符串,是在调用转大写方法
function upperCase(str:unknown){
  if(isString(str)){
    str.toUpperCase()
  }
}

可以看到,此时是str是一个string类型。

Typescript: 类型保护之类型断言、类型谓词

类型谓词的主要特点是:

  • 定义一个函数,返回类型谓词,如 s is string
  • 包含可以准确确定给定变量类型的逻辑语句,如 typeof s === 'string'

一般类型谓词用在判断变量是什么类型:

const toString = Object.prototype.toString

/**
 * 判断是否是日期对象
 * @param val
 * @returns
 */
export function isDate(val: any): val is Date {
  return toString.call(val) === '[object Date]'
}

/**
 * 判断是否是普通对象
 * @param val
 * @returns
 */
export function isPlainObject(val: any): val is Object {
  return toString.call(val) === '[object Object]'
}

/**
 * 判断是否是表单类型数据
 * @param val
 * @returns
 */
export function isFormData(val: any): val is FormData {
  return typeof val !== 'undefined' && val instanceof FormData
}

/**
 * 判断是否是URLSearchParams类型
 * @param val
 * @returns
 */
export function isURLSearchParams(val: any): val is URLSearchParams {
  return typeof val !== 'undefined' && val instanceof URLSearchParams
}

总结

关于类型保护本文介绍几种解法:

  • 类型断言as:通过断言确定变量是某一种类型,然后有针对性的进行访问

  • 关键字intypeofinstanceof:一种更为简洁的类型保护,利用了 ES 本身的语法进行判断

  • 类型谓词is:用来判断某个变量属于什么类型

转载自:https://juejin.cn/post/7244840710271877178
评论
请登录