TS短文 | 3分鐘細品 unknown 和 never 類型

“字數(shù):1737字 閱讀: 3 分鐘
大家好,今天和大家聊下讓我曾經(jīng)迷惑的兩個TS類型:unknown 和 never,不知道大家有沒有對其用法有所迷惑呢,好記性比不過爛筆頭,為了讓我不再迷惑,還是通過文字的形式整理下加深下印象比較靠譜,希望今天的分享能給大家解惑。
一、unknown 類型
unknown 類型是 TS3 新增的類型,這個類型與 any 類型類似,可以設(shè)置任何的類型值,隨后可以更改類型。因此,我們可以將變量先設(shè)置為字符串類型,然后再將其設(shè)置為數(shù)字類型,如果事先不檢查類型,使用any類型,調(diào)用了不存在的方法,編譯時不會報錯,代碼運行時才會發(fā)現(xiàn)錯誤。但是使用unknown 類型不一樣,如果不進行類型判斷,執(zhí)行相關(guān)操作編譯器就會報錯。文字說了這么多,還是 上代碼,更容易理解些。
1、關(guān)于 Any 的問題
首先我們創(chuàng)建一個 any.ts 的文件,代碼如下:
let val: any = 22;
val = "string value";
val = new Array();
val.push(33);
console.log(val);
運行編譯后的代碼,并不會報錯,也是按照我們的預(yù)期輸出:[33]
由于是 any 類型,我們可以隨意更改類型,當(dāng)變成數(shù)組類型時,我們調(diào)用push方法進行內(nèi)容操作,看似沒啥問題,如果我們開發(fā)人員,由于疏忽,打錯了一個不存在的方法,ts代碼能正常編譯,幫我們發(fā)現(xiàn)問題嗎?
let val: any = 22;
val = "string value";
val = new Array();
val.doesnotexist(33);
console.log(val);
當(dāng)運行 tsc any 命令后,你會發(fā)現(xiàn)編譯器能順利編譯,當(dāng)我們運行 node any,編譯后的代碼能正常執(zhí)行嗎?答案是顯而易見的,會報異常,你會在控制臺發(fā)現(xiàn)以下錯誤:
val.doesnotexist(33);
^
TypeError: val.doesnotexist is not a function
上述的錯誤,大家可能不會犯,但是項目大時,參與的人多時,就很難避免這樣類似的問題,因此unknown 類型出現(xiàn)了。
2、一段 unknown 類型的代碼
接下來我們來看看它是怎么解決類似的問題,我們還是從一段簡單的代碼開始,如下段代碼所示:
let val: unknown = 22;
val = "string value";
val = new Array();
val.push(33);
console.log(val);
當(dāng)你編譯此代碼時,你會立馬收到如下報錯:
Property 'push' does not exist on type 'unknown'.
是不是很奇怪,雖然我們將其類型更改為數(shù)組類型,但是編譯器不認識,它認為unknown類型,這個類型沒有push方法,當(dāng)然會報錯,除非先判斷類型,如果是相關(guān)類型且正確執(zhí)行相關(guān)方法,編譯器則會順利通過,如下段代碼所示
let val: unknown = 22;
val = "string value";
val = new Array();
if (val instanceof Array) {
val.push(33);
}
console.log(val);
雖然有些麻煩,但是相比 any 類型說,更加安全,在代碼編譯期間,就能幫我們發(fā)現(xiàn)由于類型造成的問題,因此在大多的場景,建議使用 unknown 類型替代 any。
二、never 類型
這個類型看起來有些奇怪,乍一看,看起來和void相似,但是其完全不一樣。從字面意思上來說,表示一個從來不會有返回值的函數(shù)(例:while(true) {}),一個總是會拋出錯誤的函數(shù)(function foo() { throw new Error('Not Implemented') })。那么問題來了,它和 void 類型啥區(qū)別,void 表示沒有任何類型,函數(shù)沒有返回值時(可以返回,但是沒值),我們可以設(shè)置為void 類型;never這不一樣,一個函數(shù)根本就沒返回(或者總是出錯,永遠不會有返回值)。看文字有些費勁,我們還是來看一段簡單的代碼來理解下吧,如下所示:
function alwaysThrows(): never {
throw "this will always throw";
return -1;
}
當(dāng)我們編譯上述代碼時,編譯器就會報錯,如下所示:
Type 'number' is not assignable to type 'never'.
編譯器已經(jīng)很明確的告訴了我們 never 類型不應(yīng)該返回任何值(或拋異常)。那么問題來了,這個類型有啥用呢?我們還是舉個例子來理解下吧,比如你有個 enum 枚舉類型,代碼如下所示:
enum TestNeverEnum {
FIRST,
SECOND
}
在 switch 當(dāng)中判斷 type,TS 是可以收窄類型的 (discriminated union):
enum TestNeverEnum {
FIRST,
SECOND
}
function getEnumValue(value: TestNeverEnum): string {
switch (value) {
// 這里 value 被收窄為 TestNeverEnum.FIRST
case TestNeverEnum.FIRST: return "First case";
// 這里 value 被收窄為 TestNeverEnum.SECOND
case TestNeverEnum.SECOND: return "Second case";
// returnValue 是 never 類型
default: const returnValue: never = value;
}
}
注意在 default 里面我們把被收窄為 never 的 returnValue 賦值給一個顯式聲明為 never 的變量。如果一切邏輯正確,那么這里應(yīng)該能夠編譯通過。但是假如有一天你的同事增加了TestNeverEnum 枚舉類型:
enum TestNeverEnum {
FIRST,
SECOND,
THIRD
}
然而他忘記了在 getEnumValue 里面加上針對 THIRD 的處理邏輯,這個時候在 default branch 里面 returnValue 會被收窄為 TestNeverEnum.THIRD,導(dǎo)致無法賦值給 never(因為有值返回),產(chǎn)生一個編譯錯誤。編譯器會產(chǎn)生如下的錯誤:
Type 'TestNeverEnum' is not assignable to type 'never'.
所以通過這個辦法,你可以確保 getEnumValue 方法里總是窮盡 (TestNeverEnum) 了所有 All 的可能類型,目的就是寫出類型絕對安全的代碼。
三、結(jié)束語
今天的內(nèi)容就到這里,這兩個類型你品明白了嗎?雖然內(nèi)容不多,但是需要細品 ,才能理解其應(yīng)用場景和用好他們,感謝的閱讀。
