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

原文連接

相關文章
相關標籤/搜索