詳解TypeScript中的箭頭函數如何實現重載
這個問題來自于我在網上搜索的時候,基本上清一色的翻譯官網的函數重載的章節(jié)的內容,對于我想要的箭頭函數的重載沒有太多幫助。包括官網,其實也沒有非常明確的說明箭頭函數該如何重載。
這里先直接上結論,在 ts 中,可以借助 Call Signatures 這個特性來實現箭頭函數的重載。原本是用來給函數聲明增加靜態(tài)屬性的,但是卻可以用來完成箭頭函數的類型聲明。
type Test = { (s: string): string; (s: number): number; (s: string, b: number): number; }; type getState<T> = { (): T; <K extends keyof T>(key: K): T[K]; };
本質就是定一個新的類型,鍵值就用括號包,里面就是不同的入參,函數的返回值就是鍵值即可。
但是不出意外的話,意外就會發(fā)生,在實操的時候,往往這么寫非常不 ok。
實操
實戰(zhàn)中這么寫,類型聲明是好寫的,但是函數的實現,其實并不好寫,以這樣一個函數為例:
const deal = (a: number | string, b: number | string): number | string => { if (typeof a === 'number' && typeof b === 'number') { return a + b; } if (typeof a === 'string' || typeof b === 'string') { return String(a) + String(b); } return 0; };
當入參都是 number 時,返回 number,入參有一個是 string 時,返回 string。
那么對于這樣一個函數,其實存在幾個重載的情況:
type Deal1 = { (a: number, b: number): number; (a: number, b: string): string; (a: string, b: number): string; (a: string, b: string): string; };
要想把上述類型賦值給 deal 函數,會出現返回值匹配不上的問題。
// ts 會提示類型錯誤: // Type '(a: number | string, b: number | string) => number | string' is not assignable to type 'Deal1'. // Type 'string | number' is not assignable to type 'number'. const deal: Deal1 = ( a: number | string, b: number | string, ): number | string => { if (typeof a === 'number' && typeof b === 'number') { return a + b; } if (typeof a === 'string' || typeof b === 'string') { return String(a) + String(b); } ??????? return 0; };
從現象來看,ts 對于這樣的寫法在做檢查的時候,會將當前函數對重載的幾個類型都進行檢查,看看類型上是否能夠賦值,相當于:
// 偽代碼,理解意思就行 check1: (a: number, b: number) => number = (a: number | string, b: number | string) => number | string); check2: (a: number, b: string) => string = (a: number | string, b: number | string) => number | string); check3: (a: string, b: number) => string = (a: number | string, b: number | string) => number | string); check4: (a: string, b: string) => string = (a: number | string, b: number | string) => number | string);
對于入參,由于是逆變位置,所以 number = number | string 能夠賦值,所以參數的類型能夠通過校驗,而返回值屬于順變位置,所以 number = number | string 是不能通過類型校驗的。
想要將返回值賦值成功,返回值必須是 number & string 或者 any,前者就是 never 了,此時會發(fā)現雖然賦值通過了 deal 的校驗,但是函數的實現中,就會報返回值錯誤的問題。如果改成 any,那么雖然不會報錯,但是在函數中就缺失了對返回值的檢查。如下:
// 返回值改為 number & string, // 賦值處能夠避免類型錯誤 const deal: Deal1 = ( a: number | string, b: number | string, ): number & string => { if (typeof a === 'number' && typeof b === 'number') { // 此時返回值是 never,此處會報類型錯誤 return a + b; } if (typeof a === 'string' || typeof b === 'string') { // 此時返回值是 never,此處會報類型錯誤 return String(a) + String(b); } // 此時返回值是 never,此處會報類型錯誤 return 0; }; // 返回值改為 any, // 賦值處能夠避免類型錯誤 const deal: Deal1 = (a: number | string, b: number | string): any => { if (typeof a === 'number' && typeof b === 'number') { return a + b; } if (typeof a === 'string' || typeof b === 'string') { return String(a) + String(b); } // 此處寫任何類型都不會拋錯,缺失了原本的期望的校驗 return undefined; };
那么目前看,想要用這種方式實現箭頭函數的重載,就只能將返回值設定為 any,這樣,雖然在用戶使用的時候能夠進行非常好的類型提示,但是開發(fā)者本身不能再借助 ts 完成對這個函數的返回值的校驗。
此時還有一種寫法,就是 as,寫法如下:
const deal = ((a: string | number, b: number | string): number | string => { if (typeof a === 'number' && typeof b === 'number') { return a + b; } if (typeof a === 'string' || typeof b === 'string') { return String(a) + String(b); } return 0; }) as Deal1;
這樣的寫法,即能夠滿足函數本身的返回值校驗 (可以把 0 改成其他類型試試),同時,又具備了 Deal1 的重載的類型聲明:
// case1: number const case1 = deal(1, 2); // case2: string const case2 = deal('1', 2); // case3: never,入參類型錯誤 const case3 = deal({}, 2);
最佳實踐
附上完整的代碼:
// 重載類型聲明 type Deal1 = { (a: number, b: number): number; (a: number, b: string): string; (a: string, b: number): string; (a: string, b: string): string; }; const deal = (( // 此時入參,返回值的類型可自行限制 // 無需掛念 Deal1 中的定義 a: string | number, b: number | string, ): number | string => { if (typeof a === 'number' && typeof b === 'number') { return a + b; } if (typeof a === 'string' || typeof b === 'string') { return String(a) + String(b); } // 這樣寫,原函數具備校驗的能力 return 0; // 通過 as 指定類型 }) as Deal1; const case1 = deal(1, 2); const case2 = deal('1', 2); const case3 = deal({}, 2);
核心就是原函數類型寫法一致,重載的類型通過 as 進行賦值,這樣就兼顧了類型提示和原函數的類型校驗。
其他方法
由于 type 和 interface 的用法在此處并無歧義,所以換成 interface 也是 ok。
另一種就是函數的交叉,也是實現重載的一種方案,如下:
type Deal3 = ((a: number, b: number) => number) & ((a: number, b: string) => string) & ((a: string, b: number) => string) & ((a: string, b: string) => string);
總結
本文介紹了 TS 實現箭頭函數重載的幾種方案,借助了 Call Signatures 的特性。
同時給出了這樣寫,在實戰(zhàn)中可能會遇到的類型不匹配的問題及解決方案。希望能夠對遇到同樣問題的同學有所幫助吧。
到此這篇關于詳解TypeScript中的箭頭函數如何實現重載的文章就介紹到這了,更多相關TypeScript函數重載內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
ES6中l(wèi)et、const的區(qū)別及變量的解構賦值操作方法實例分析
這篇文章主要介紹了ES6中l(wèi)et、const的區(qū)別及變量的解構賦值操作方法,結合實例形式分析了ES6中l(wèi)et、const的功能、原理、使用方法及數組、字符串、函數參數等解構賦值相關操作技巧,需要的朋友可以參考下2019-10-10