天天看點

什麼?TS 函數類型重載還可以動态生成?

重載是指一個函數可以有不同的參數和傳回值,也就是有不同的函數簽名。

ts 支援函數重載,可以為同一個函數定義多個不同的類型:

重載的寫法一共有三種(估計大多數人隻會一種):

declare function func(name: string): string;
declare function func(name: number): number;      

這種大家比較常用,聲明兩個同名函數,就能達到重載的目的:

什麼?TS 函數類型重載還可以動态生成?

函數可以用 interface 的方式聲明,同樣,也可以用 interface 的方式聲明函數重載:

什麼?TS 函數類型重載還可以動态生成?

函數類型可以取交叉類型,也就是多種類型都可以,其實也是函數重載的意思:

什麼?TS 函數類型重載還可以動态生成?

重載雖然是很有用的特性,但有的時候重載多了寫起來還是挺麻煩的。

比如 ts 提供的 lib.dom.ts 裡就有這樣的類型定義:

什麼?TS 函數類型重載還可以動态生成?

因為每種參數對應不同的傳回值,是以它就重載了這麼多。

這樣寫起來也太麻煩了吧,能不能用類型程式設計動态生成呢?

考慮下重載的三種寫法,declare 和 interface 不行,但是 & 可以呀,能不能我傳入一個聯合類型,然後它給傳回交叉類型呢?

比如這樣:

什麼?TS 函數類型重載還可以動态生成?

都提示出來了肯定是可以的,我們看下咋實作的吧:

聯合轉交叉

函數參數有逆變的性質,也就是類型縮小,比如參數能同時傳 A、B、C 的話,參數類型怎麼定義?

肯定是 A、B、C 的交集呀,也就是 A & B & C 的交叉類型,這樣就可以接收 A、B、C 的參數了。

可以利用這個性質實作聯合轉交叉。

type UnionToIntersection<U> =
    (U extends U ? (x: U) => unknown : never) extends (x: infer R) => unknown
        ? R
        : never      

測試一下:

什麼?TS 函數類型重載還可以動态生成?

這裡的類型參數 U 是傳入的聯合類型,加一個 U extends U 是為了觸發分布式條件類型的特性。

什麼是分布式條件類型呢?

當類型參數為聯合類型,并且在條件類型左邊直接引用該類型參數的時候,TypeScript 會把每一個元素單獨傳入來做類型運算,最後再合并成聯合類型,這種文法叫做分布式條件類型。

比如這樣一個聯合類型:

type Union = 'a' | 'b' | 'c';      

我們想把其中的 a 大寫,就可以這樣寫:

type UppercaseA<Item extends string> =
    Item extends 'a'      
什麼?TS 函數類型重載還可以動态生成?

回到聯合轉交叉的這個進階類型:

什麼?TS 函數類型重載還可以動态生成?

加一個 U extends U 或者 U extends any 就能觸發分布式條件類型的特性,讓聯合類型分成每一個類型單獨傳入做計算,最後把結果合并成聯合類型。

然後再把它放到函數參數的位置,構造一個函數類型,通過模式比對的方式提取參數的類型到 infer 聲明的局部變量 R 裡傳回。

這樣的結果就是交叉類型。

原因上面說過了,函數參數有逆變的性質,傳入聯合類型會傳回交叉類型。

什麼?TS 函數類型重載還可以動态生成?

實作了聯合轉交叉之後,函數重載也就可以寫出來了:

比如三個重載的傳回值分别是 Aaa、Bbb、Ccc:

什麼?TS 函數類型重載還可以動态生成?

我們想基于這個生成重載的類型定義,傳入聯合類型傳回重載的函數:

什麼?TS 函數類型重載還可以動态生成?

就可以這樣寫:

type UnionToOverloadFunction<T extends keyof ReturnValueMap> =
    UnionToIntersection<
        T extends any ? (type: T) => ReturnValueMap[T] : never      

類型參數 T 是 ReturnValueMap 裡的 key,限制為 keyof ReturnValueMap。

通過 T extends any 觸發聯合類型在分布式條件類型中的分發特性,讓 'aaa' 'bbb' 'ccc' 分别傳入做計算,傳回構造出的函數類型的聯合。

我們先單獨測試下這部分:

什麼?TS 函數類型重載還可以動态生成?

可以看到傳回的是構造出的函數類型的聯合類型。

然後就用上面的 UnionToIntersection 轉交叉就可以了:

什麼?TS 函數類型重載還可以動态生成?

這樣就實作了重載函數的動态生成:

什麼?TS 函數類型重載還可以動态生成?

對比下最開始那種寫法:

什麼?TS 函數類型重載還可以動态生成?

是不是清爽多了!而且還可以寫一些動态邏輯。

總結

ts 函數重載一共有三種寫法: declare function、interface、交叉類型 &。

當重載比較多的時候,直接列出來還是比較麻煩的,這時候可以用類型程式設計來動态生成函數重載。

我們實作了聯合轉交叉,利用了函數參數的逆變性質,也就是當參數可能是多個類型時,會傳回它們的交叉類型。

繼續閱讀