理解 TypeScript 的 Never 類型
今天我們就來深入討論 never 類型,并介紹可能遇到的情況。
1. never 的特點
TypeScript使用never關(guān)鍵字來表示邏輯上不應(yīng)該發(fā)生的情況和控制流。實際上,我們在工作中不會常遇到使用 never 的情況,但是還是很有必要了解它是如何有助于 TypeScript 的類型安全的。
官方文檔對 never 的描述:
never 類型是任何類型的子類型,也可以賦值給任何類型;但是,沒有類型是never的子類型或可以賦值給never類型(never 本身除外)。
也就是說,可以將never類型的變量分配給任何其他變量,但不能將其他變量分配給never。下面來看一個例子:
const throwErrorFunc = () => { throw new Error("error") };
let neverVar: never = throwErrorFunc()
const myString = ""
const myInt:number = neverVar;
neverVar = myString // Type 'string' is not assignable to type 'never'
我們可以暫時忽略 throwErrorFunc 的功能,只需知道,這樣可以初始化類型為 never 的變量。
從上面的代碼中可以看到,可以將 never 類型的變量 neverVar 分配給 number 類型的變量myInt。但是,不能將 string 類型的 myString 變量分配給 neverVar,這樣分配會報錯。這也就是上面所說的,不能將任何其他類型的變量分配給 never,即使是 any 類型的變量。
2. 函數(shù)中的 never
TypeScript 使用 never 作為那些無法達到的終點的函數(shù)的返回值類型。主要有兩種情況:
函數(shù)拋出一個錯誤異常。
函數(shù)包含一個無限循環(huán)。
來看上面提到的 throwErrorFunc 函數(shù),TypeScript 就會推斷此函數(shù)的返回類型為 never:
const throwErrorFunc = () => {
throw new Error("error")
};
另一種情況就是如果有一個一直為真的表達式產(chǎn)生了無限循環(huán),它沒有中斷或者返回語句。TypeScript 就會推斷此函數(shù)的返回類型為 never:
const output = () => {
while (true) {
console.log("循環(huán)");
}
};
3. never 和 void 的區(qū)別
那什么是 void 類型呢?我們有 void 為什么還要 never 類型呢?
never 和 void 的主要區(qū)別在于,void 類型的值可以是 undefined 或 null。
TypeScript 對不返回任何內(nèi)容的函數(shù)使用 void。如果沒有為函數(shù)指定返回類型,并且在代碼中沒有返回任何內(nèi)容,TypeScript 將推斷其返回類型為void。在TypeScript中,不返回任何內(nèi)容的 void 函數(shù)實際上返回的是 undefined。
來看一個例子:
const functionWithVoidReturnType = () => {};
console.log(functionWithVoidReturnType()); // undefined
這里,TypeScript 會推斷此函數(shù)的返回類型為 void。我們通常會忽略 void 函數(shù)的返回值。
這里需要注意:根據(jù) never 類型的特征,我們不能將 void 指定給 never:
const myVoidFunction = () => {}
neverVar = myVoidFunction() // ERROR: Type 'never' is not assignable to type 'void'
4. never 作為可變類型守
如果變量被一個永遠不可能為 true 的類型保護縮小范圍,那么變量就可能成為 never類型。通常,這表明條件邏輯存在缺陷。
來看下面的例子:
const unExpectedResult = (myParam: "this" | "that") => {
if (myParam === "this") {
} else if (myParam === "that") {
} else {
console.log({ myParam })
}
}
在這個例子中,當函數(shù)執(zhí)行到 console.log({ myParam }) 時,myParam 的類型將為 never。
這是因為我們將 myParam 的類型設(shè)置為了 this 或 that。由于 TypeScript 認為 myParam 類型屬于兩個類型之一,所以從邏輯上講,第三個 else 語句永遠不會出現(xiàn)。所以TypeScript 就會將參數(shù)類型設(shè)置為 never。
5. 詳盡檢查
在實踐中可能會用到 never 的一個地方就是進行詳細的檢查。這有助于確保我們處理了代碼中的每個邊緣情況。
下面來看看如何使用詳盡檢查為 switch 語句添加更好的類型安全性:
type Animal = "cat" | "dog" | "bird"
const shouldNotHappen = (animal: never) => {
throw new Error("error")
}
const myPicker = (pet: Animal) => {
switch(pet) {
case "cat": {
// ...
return
}
case "dog": {
// ...
return
}
}
return shouldNotHappen(pet)
}
當添加 「return shouldNotHappen(pet)」時,會看到一個錯誤提示:
這里的錯誤提示告訴我們,忘記包含在 switch 語句中的情況。這是一種獲得編譯時安全性并確保處理 switch 語句中的所有情況的巧妙模式。
6. 結(jié)論如你所見,TypeScript 中的 never 類型在特定情況很有用。大多數(shù)情況下,never 表明代碼存在缺陷。
但在某些情況下,例如詳盡檢查,它可以成為幫助編寫更安全的 TypeScript 代碼的好工具。