嘿,歡迎閱讀我們的 TypeScript Narrowing 系列的另一篇文章。 在這篇文章中,我將解釋:
這是我們系列的第三篇文章,如果你還沒有看過之前的文章,我強(qiáng)烈建議你去看看,它們?yōu)槭照峁┝藞?jiān)實(shí)的基礎(chǔ)。
類型謂詞
在上一篇文章中,我們探討了基本的類型保護(hù)運(yùn)算符。 現(xiàn)在我想向你展示類型保護(hù)函數(shù)。
例如,如果您需要檢查名為 value 的變量是否為字符串,則可以使用 typeof 運(yùn)算符。 但你也可以做的是創(chuàng)建一個(gè)名為 isString() 的函數(shù),它接收一個(gè)參數(shù)并在給定參數(shù)是字符串時(shí)返回 true。
const isString = (value: any): boolean => typeof value === ‘string’;
還記得上一篇文章中的 formatErrorMessage() 函數(shù)嗎?
const formatErrorMessage = ( value: null | undefined | string | Error | Warning): string => { const prefix = ‘Error: ‘; // If it’s falsy (null, undefined, empty string), return “Unknown” with the prefix if (!value) { return prefix + ‘Unknown’; } // If it’s a string, return the string with the prefix if (typeof value === ‘string’) { return prefix + value; } // If it’s a Warning, return the Warning.text with the prefix if (‘text’ in value) { return prefix + value.text; } // If it’s an Error, return the Error.message with the prefix if (value instanceof Error) { return prefix + value.message; } // We will never reach here throw new Error(`Invalid value type`);};interface Warning { text: string;}
讓我們從中刪除 typeof 運(yùn)算符并使用 isString() 代替。
const formatErrorMessage = ( value: null | undefined | string | Error | Warning): string => { const prefix = ‘Error: ‘; // If it’s falsy (null, undefined, empty string), return “Unknown” with the prefix if (!value) { return prefix + ‘Unknown’; } // If it’s a string, return the string with the prefix if (isString(value)) { return prefix + value; } // If it’s a Warning, return the Warning.text with the prefix if (‘text’ in value) { return prefix + value.text; } // If it’s an Error, return the Error.message with the prefix if (value instanceof Error) { return prefix + value.message; } // We will never reach here throw new Error(`Invalid value type`);};interface Warning { text: string;}
相同的代碼,我們只是在一個(gè)函數(shù)中隔離了守衛(wèi),對(duì)吧? 不,它壞了。 TypeScript 沒有將類型縮小為字符串,防護(hù)不起作用。
事情是這樣的,isString() 返回一個(gè)布爾值,我們知道這個(gè)布爾值的含義。
const isString = (value: any): boolean => typeof value === ‘string’;
這意味著參數(shù)是一個(gè)字符串。 但是 TypeScript 不知道那個(gè)布爾值是什么意思,所以讓我們教它。
與其說我們的函數(shù)返回一個(gè)布爾值,不如說我們的函數(shù)返回問題的答案:“這個(gè)參數(shù)是字符串嗎?”。
鑒于我們的參數(shù)的名稱是 value,我們使用以下語法來做到這一點(diǎn):value 是字符串。
const isString = (value: any): value is string => typeof value === ‘string’;
現(xiàn)在 TypeScript 知道 isString() 是一個(gè)類型保護(hù)并且我們的 formatErrorMessage() 函數(shù)可以正確編譯。
我們的 isString() 函數(shù)的返回類型不再只是一個(gè)布爾值,它是一個(gè)“類型謂詞”。
因此,要制作自定義類型保護(hù),您只需定義一個(gè)返回類型謂詞的函數(shù)。
所有類型謂詞都采用 { parameter } is { Type } 的形式。
未知類型
在我們繼續(xù)之前的快速提示:
如果我們使用未知類型,我們的代碼會(huì)更安全,而不是在我們的自定義保護(hù)參數(shù)中使用類型 any。
const isString = (value: unknown): value is string => typeof value === ‘string’;
我制作了一個(gè)一分鐘的視頻來解釋任何和未知之間的區(qū)別,鏈接在參考資料中。
自定義警衛(wèi)
讓我們通過將 formatErrorMessage() 函數(shù)中的所有檢查轉(zhuǎn)換為自定義守衛(wèi)來鍛煉我們的知識(shí)。
我們已經(jīng)有了字符串保護(hù),現(xiàn)在我們需要警告、錯(cuò)誤和虛假類型的保護(hù)。
錯(cuò)誤防護(hù)
Error 的保護(hù)非常簡單,我們只是將 instanceof 操作符檢查隔離在一個(gè)函數(shù)中。
const isError = (value: unknown): value is Error => value instanceof Error;
警戒衛(wèi)士
但另一方面,Warning 守衛(wèi)并不是那么簡單。
TypeScript 允許我們使用 in 運(yùn)算符,因?yàn)槲覀兊?value 參數(shù)可以是有限數(shù)量的類型,并且它們都是對(duì)象。
const formatErrorMessage = ( value: null | undefined | string | Error | Warning): string => { const prefix = ‘Error: ‘; // If it’s falsy (null, undefined, empty string), return “Unknown” with the prefix if (!value) { return prefix + ‘Unknown’; } // If it’s a string, return the string with the prefix if (isString(value)) { return prefix + value; } // If it’s a Warning, return the Warning.text with the prefix if (‘text’ in value) { return prefix + value.text; } // If it’s an Error, return the Error.message with the prefix if (isError(value)) { return prefix + value.message; } // We will never reach here throw new Error(`Invalid value type`);};interface Warning { text: string;}
但是如果我們創(chuàng)建一個(gè)函數(shù)并說我們的參數(shù)是未知的,那么它可以是任何東西。 包括原始類型,這會(huì)引發(fā)錯(cuò)誤,因?yàn)槲覀冎荒茉趯?duì)象中使用 in 運(yùn)算符。
interface Warning { text: string;}const isWarning = (value: unknown): value is Warning => ‘text’ in value; // Compilation error
解決方案是在使用 in 運(yùn)算符之前確保我們的參數(shù)是一個(gè)有效的對(duì)象。 我們還需要確保它不為空。
interface Warning { text: string;}const isWarning = (value: unknown): value is Warning => typeof value === ‘object’ && value !== null && ‘text’ in value;
假守衛(wèi)
對(duì)于虛假值守衛(wèi),我們首先需要定義一個(gè)類型,其值被認(rèn)為是虛假的。
type Falsy = false | 0 | -0 | 0n | ” | null | undefined;
我在這里不包括 NaN,因?yàn)?TypeScript 中沒有 NaN 類型。
type Falsy = false | 0 | -0 | 0n | ” | null | undefined | ~~NaN~~;
NaN 的類型是數(shù)字,并非所有數(shù)字都是假的,所以這就是我們不處理 NaN 的原因。
typeof NaN;//=> number
有一個(gè)提議將 NaN 添加為一種類型——以及整數(shù)、浮點(diǎn)數(shù)和無窮大。 我認(rèn)為這很好,擁有這些類型會(huì)很有幫助。
// Proposaltype number = integer | float | NaN | Infinity;
我將在參考文獻(xiàn)中留下該提案的鏈接。
無論如何,現(xiàn)在我們有了 Falsy 類型,我們可以創(chuàng)建一個(gè) falsy 值守衛(wèi)。
請記住,如果一個(gè)值在轉(zhuǎn)換為布爾值時(shí)被認(rèn)為是假的,那么它就是假的。 因此,要檢查我們的值是否為假,我們可以使用抽象相等來查看它是否被轉(zhuǎn)換為假。
type Falsy = false | 0 | -0 | 0n | ” | null | undefined;const isFalsy = (value: unknown): value is Falsy => value == false;
帶有自定義警衛(wèi)的 formatErrorMessage()
就是這樣,我們現(xiàn)在擁有了 formatErrorMessage() 函數(shù)所需的所有自定義守衛(wèi)。
// FUNCTIONconst formatErrorMessage = ( value: null | undefined | string | Error | Warning): string => { const prefix = ‘Error: ‘; // If it’s falsy (null, undefined, empty string), return “Unknown” with the prefix if (isFalsy(value)) { return prefix + ‘Unknown’; } // If it’s a string, return the string with the prefix if (isString(value)) { return prefix + value; } // If it’s a Warning, return the Warning.text with the prefix if (isWarning(value)) { return prefix + value.text; } // If it’s an Error, return the Error.message with the prefix if (isError(value)) { return prefix + value.message; } // We will never reach here throw new Error(`Invalid value type`);};// GUARDSconst isString = (value: unknown): value is string => typeof value === ‘string’;const isError = (value: unknown): value is Error => value instanceof Error;interface Warning { text: string;}const isWarning = (value: unknown): value is Warning => typeof value === ‘object’ && value !== null && ‘text’ in value;type Falsy = false | 0 | -0 | 0n | ” | null | undefined;const isFalsy = (value: unknown): value is Falsy => value == false;
獎(jiǎng)勵(lì):通過排除縮小范圍
在我們結(jié)束之前,我想向你展示一些東西。
虛假值的列表是有限的,對(duì)嗎?
1. `false`2. `0` `-0` `0n` representations of zero3. ““ `””` `”` empty string4. `null`5. `undefined`6. `NaN` not a number
但另一方面,真實(shí)值是無限的。 所有不虛假的價(jià)值觀都是真實(shí)的。
那么,如何為真實(shí)值創(chuàng)建類型保護(hù)呢?
誠實(shí)守衛(wèi)
訣竅是排除虛假類型。
我們不是檢查我們的值是否為真,而是檢查它是否_不_假。
type Truthy = Exclude;const isTruthy = (value: T): value is Truthy => value == true;// Testconst x = ‘abc’ as null | string | 0;if (isTruthy(x)) { x.trim(); // `x: string`}
我經(jīng)常使用這個(gè)技巧,我們將在以后的文章中再次看到它。
結(jié)論
參考資料和其他鏈接如下。
如果您還沒有,請?jiān)谏缃幻襟w上點(diǎn)贊、訂閱和關(guān)注我們。 這有助于我們成長,從而為您帶來更多免費(fèi)內(nèi)容。 這是雙贏的。