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