Typescript 夜点心:自定义类型守卫

今天的夜点心关于 Typescript 中的自定义类型守卫git

什么是类型守卫

TS 在遇到如下这些条件语句时,会在语句的块级做用域内「收紧」变量的类型,这种类型推断的行为称做类型守卫 (Type Guard)。github

  • 类型判断:typeof
  • 实例判断:instanceof
  • 属性判断:in
  • 字面量相等判断:==, ===, !=, !==

(这里列举的是比较经常使用的 4 种)typescript

类型守卫能够帮助咱们在块级做用域中得到更为精确的变量类型,从而减小没必要要的类型断言。下面经过一些具体的例子来帮助你们理解这个看起来有点抽象的概念:函数

类型判断:typeof

function test(input: string | number) {
  if (typeof input == 'string') {
    // 这里 input 的类型「收紧」为 string
  } else {
    // 这里 input 的类型「收紧」为 number
  }
}
复制代码

实例判断:instanceof

class Foo {}
class Bar {}

function test(input: Foo | Bar) {
  if (input instanceof Foo) {
    // 这里 input 的类型「收紧」为 Foo
  } else {
    // 这里 input 的类型「收紧」为 Bar
  }
}
复制代码

属性判断:in

interface Foo {
  foo: string;
}

interface Bar {
  bar: string;
}

function test(input: Foo | Bar) {
  if ('foo' in input) {
    // 这里 input 的类型「收紧」为 Foo
  } else {
    // 这里 input 的类型「收紧」为 Bar
  }
}
复制代码

字面量相等判断 ==, !=, ===, !==

type Foo = 'foo' | 'bar' | 'unknown';

function test(input: Foo) {
  if (input != 'unknown') {
    // 这里 input 的类型「收紧」为 'foo' | 'bar'
  } else {
    // 这里 input 的类型「收紧」为 'unknown'
  }
}
复制代码

上述的「收紧」做用所带来的便利性,你极可能已经在开发中受惠过不少次了,只是不知道该怎么称呼它。值得注意的是,一旦上述条件不是直接经过字面量书写,而是经过一个条件函数来替代时,类型守卫便失效了,以下面的 isString 函数:优化

function isString (input: any) {
  return typeof input === 'string';
}

function foo (input: string | number) {
  if (isString(input)) {
    // 这里 input 的类型没有「收紧」,仍为 string | number
  } else {
    // 这里也同样
  }
}
复制代码

这是由于 TS 只能推断出 isString 是一个返回布尔值的函数,而并不知道这个布尔值的具体含义。然而在平常的开发中,出于优化代码结构等目的,上述的「替换」情形是很是常见的,这时为了继续得到类型守卫的推断能力,就要用到自定义守卫。ui

自定义守卫

自定义守卫经过 {形参} is {类型} 的语法结构,来给上述返回布尔值的条件函数赋予类型守卫的能力。例如上面的 isString 函数能够被重写为:spa

function betterIsString (input: any): input is string { // 返回类型改成了 `input is string`
  return typeof input === 'string';
}
复制代码

这样 betterIsString 便得到了与 typeof input == 'string' 同样的守卫效果,并具备更好的代码复用性。code

因为自定义守卫的本质是一种「类型断言」,于是在自定义守卫函数中,你能够经过任何逻辑来实现对类型的判断,不须要受限于前面的 4 种条件语句。好比以下的“鸭子”类型守卫函数认为只要一个对象知足有头盔有斗篷有内裤有皮带,它就必定是“蝙蝠侠”的实例:对象

class SuperHero { // 超级英雄
  readonly name: string;
}
class Batman extends SuperHero { // 蝙蝠侠继承自超级英雄
  private muchMoney: true; // 私有不少钱
}

// 判断任意对象是否是蝙蝠侠的函数
function isBatman (man: any): man is Batman {
  return man && man.helmet && man.underwear && man.belt && man.cloak;
}

function foo (hero: SuperHero) {
  if (isBatman(hero)) {
    // hero 是蝙蝠侠
  } else {
    // hero 是别的超级英雄
  }
}
复制代码

在项目中合理地使用类型守卫和自定义守卫,能够帮助咱们减小不少没必要要的类型断言,同时改善代码的可读性。继承

最后一个问题:除了蝙蝠侠,你还能想到别的知足有头盔有斗篷有内裤有皮带超级英雄吗?

扩展阅读

Type Guard - Typescript Deep Dive

原文连接

相关文章
相关标签/搜索