第十六節(jié): TypeScript類型謂詞( is關(guān)鍵字 )

TypeScript 中的 is 關(guān)鍵字,它被稱為類型謂詞,用來判斷一個變量屬于某個接口或類型。

1. 類型謂詞的基本使用

is 關(guān)鍵字一般用于函數(shù)返回值類型中,判斷參數(shù)是否屬于某一類型,并根據(jù)結(jié)果返回對應(yīng)的布爾類型。

例如:通過判斷將字符串類型并轉(zhuǎn)為大寫

// 判斷參數(shù)是否為string類型, 返回布爾值
function isString(s:unknown):boolean{
  return typeof s === 'string'
}

// 參數(shù)轉(zhuǎn)為大寫函數(shù)
// 直接使用轉(zhuǎn)大寫方法報錯, str有可能是其他類型
function upperCase(str:unknown){
  str.toUpperCase()
  // 類型“unknown”上不存在屬性“toUpperCase”。
}

// 判斷參數(shù)是否為字符串,是在調(diào)用轉(zhuǎn)大寫方法
function ifUpperCase(str:unknown){

  if(isString(str)){
    str.toUpperCase()
    // (parameter) str: unknown
    // 報錯:類型“unknown”上不存在屬性“toUpperCase”
  }
}

示例中我們雖然判斷了參數(shù)strstring類型, 但是條件為true時, 參數(shù)str的類型還是unknown.也就是說這個條件判斷并沒有更加明確str的具體類型


此時,可以在判斷是否為string類型的函數(shù)返回值類型使用is關(guān)鍵詞(即類型謂詞)

例如:

// 判斷參數(shù)是否為string類型, 返回布爾值
function isString(s:unknown):s is string{
  return typeof s === 'string'
}

// 判斷參數(shù)是否為字符串,是在調(diào)用轉(zhuǎn)大寫方法
function ifUpperCase(str:unknown){

  if(isString(str)){
    str.toUpperCase()
    // (parameter) str: string
  }
}

s is string不僅返回boolean類型判斷參數(shù)s是不是string類型, 同時明確的string類型返回到條件為true的代碼塊中.

因此當我們判斷條件為true, 即strstring類型時, 代碼塊中str類型也轉(zhuǎn)為更明確的string類型

類型謂詞的主要特點是:

  • 返回類型謂詞,如 s is string
  • 包含可以準確確定給定變量類型的邏輯語句,如 typeof s === 'string'


接下來看一個聯(lián)合類型的問題, 類型謂詞是最好的解決方法


2. 了解聯(lián)合類型的問題

在TypeScript中,我們往往會定義一個聯(lián)合類型,來解決一些業(yè)務(wù)中復(fù)雜數(shù)據(jù)的處理

例如:

// 接口 interfaceA
interface interfaceA {
  name: string;
  age: number;
}

// 接口 interfaceB
interface interfaceB {
  name: string;
  phone: number;
}

// 推斷類型
const obj1 = { name: "andy", age: 2 };
// const obj1: {name: string;age: number;}

const obj2 = { name: "andy", phone: 2 };
// const obj2: {name: string;phone: number;}

// 創(chuàng)建數(shù)組
// arr1, 創(chuàng)建兩個interfaceA[]數(shù)組, 數(shù)組每一項都是 obj1
const arr1 = new Array<interfaceA>(2).fill(obj1);
// const arr1: interfaceA[]

// arr2, 創(chuàng)建兩個interfaceB[]數(shù)組, 數(shù)組每一項都是 obj2
const arr2 = new Array<interfaceB>(2).fill(obj2);
// const arr2: interfaceB[]

// 合并兩種類型數(shù)組,
//  arr3類型就是一個聯(lián)合數(shù)組
const arr3 = [...arr1, ...arr2];
// const arr3: (interfaceA | interfaceB)[]


const target = arr3[0];
// const target: interfaceA | interfaceB  

// Ok獲取兩個結(jié)構(gòu)共有的屬性
console.log(target.name);

// 獲取兩個接口不同的屬性報錯:
console.log(target.phone);
// 報錯: 類型“interfaceA”上不存在屬性“phone”

console.log(target.age);
// 報錯:  類型“interfaceB”上不存在屬性“age”


示例代碼中我們定義了一個接口interfaceA,一個接口interfaceB,他們各自對應(yīng)一個具體的數(shù)據(jù)類型,但我們有時需要將這2個數(shù)據(jù)數(shù)組進行合并,TypeScript會自動推斷這個數(shù)據(jù)的類型為聯(lián)合類型數(shù)組

const arr3: (interfaceA | interfaceB)[]


當我們通過數(shù)組操作取出數(shù)組里的元素時,這個元素的類型其實也是這個聯(lián)合類型,但我們需要取值的時候,由于name屬于interfaceAinterfaceB共有的,即交叉屬性

TypeScript判定無風(fēng)險,可用。但我們使用phone或者age這種不交叉的屬性時候,TypeScript會報錯,報錯信息如下

// 類型“interfaceA | interfaceB”上不存在屬性“phone”。
// 類型“interfaceA”上不存在屬性“phone”。ts(2339)


3. 解決聯(lián)合類型問題的方法

3.1 將所有非交叉屬性設(shè)置 為可選屬性

例如:

// 接口 interfaceA
interface interfaceA {
  name: string;
  age?: number;
}

// 接口 interfaceB
interface interfaceB {
  name: string;
  phone?: number;
}

說明: 這種方式也不是特別好, 因為對于interfaceB來說,可能phone屬性就是必選的,定義成可選屬性是一種逃避,且不安全


3.2 使用斷言

在每次使用到非交叉屬性是使用斷言

例如:

// 使用斷言
console.log((target as interfaceB).phone);
console.log((<interfaceA>target).age);

但也不是特別好,因為難道在一個作用域下,我每次取值都要斷言,麻煩且有風(fēng)險,


3.3 條件判斷

使用in運算符判斷屬性是否屬于當前對象

例如:

// 通過使用in運算符 的條件判斷, 縮小target類型
if('phone' in target){
  console.log(target.phone);
  // const target: interfaceB
}


if('age' in target){
  console.log(target.age);
  // const target: interfaceA
}

缺點和斷言一樣, 每次都需要判斷


3.4 類型 謂詞

使用TypeScript中的自定義類型保護類型謂詞.

我們需要創(chuàng)建一個函數(shù),在這個函數(shù)的方法體中,我們不僅要檢查target 變量是否含有 age屬性,而且還要告訴 TypeScript 編譯器,如果上述邏輯語句的返回結(jié)果是 true,那么當前判斷的target 變量值的類型是 interfaceA類型


創(chuàng)建一個自定義類型保護函數(shù) —— isInterfaceA,它的具體實現(xiàn)如下:

// 聯(lián)合類型
type interfaceAB = interfaceA | interfaceB;

// 自定義類型保護函數(shù)
const isInterfaceA = (item: interfaceAB): item is interfaceA => {
  return (item as interfaceA).age !== undefined;
};

// 判斷target 屬于哪個類型
if (isInterfaceA(target)) {
  console.log(target.age); //target的類型為interfaceA
} else {
  console.log(target.phone); //target的類型為interfaceB
}

你可以傳遞任何值給 isInterfaceA 函數(shù),用來判斷它是不是interfaceA。isInterfaceA函數(shù)與普通函數(shù)的最大區(qū)別是,該函


4. 通過泛型解決類型謂詞復(fù)用問題

如果你要檢查的類型很多,那么為每種類型創(chuàng)建和維護唯一的類型保護可能會變得很繁瑣。針對這個問題,我們可以利用 TypeScript 的另一個特性 —— 泛型,來解決復(fù)用問題:

例如:定義通用類型保護函數(shù)


// 通過泛型定義通用類型保護函數(shù)
function isOfType<T>(
  target: unknown,
  prop: keyof T
): target is T {
  return (target as T)[prop] !== undefined;
}

// 類型保護
if (isOfType<interfaceA>(target, "age")) {
  console.log(target.age);
} 
if (isOfType<interfaceB>(target, "phone")) {
  console.log(target.phone);
}

示例中通過使用類型保護函數(shù)來縮窄類型。


5. 小結(jié):

is 關(guān)鍵字經(jīng)常用來封裝”類型保護函數(shù)”,通過和函數(shù)返回值的比較,從而縮小參數(shù)的類型范圍,所以類型謂詞 is 也是一種類型保護。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容