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