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ù)str是string類型, 但是條件為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, 即str為string類型時, 代碼塊中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屬于interfaceA和interfaceB共有的,即交叉屬性
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 也是一種類型保護。