如何聲明 Currying 函數(shù)的類型?
Challenge
在本次挑戰(zhàn)中,您需要為 Currying 函數(shù)聲明相應(yīng)的類型,以幫助 TypeScript 編譯器推斷出正確的類型。
declare function Currying(fn: any): any
const curried1 = Currying((a: string, b: number, c: boolean) => true)
const curried2 = Currying((a: string, b: number, c: boolean, d: boolean, e: boolean, f: string, g: boolean) => true)
const curried3 = Currying(() => true)
type cases = [
Expect<Equal<
typeof curried1,
(a: string) => (b: number) => (c: boolean) => true>>,
Expect<Equal<
typeof curried2,
(a: string) => (b: number) => (c: boolean) => (d: boolean) => (e: boolean) => (f: string) => (g: boolean) => true
>>,
Expect<Equal<typeof curried3, () => true>>,
]
在上面的代碼中,我們使用了兩個工具類型 Expect 和 Equal,它們的實現(xiàn)代碼如下:
type Expect<T extends true> = T
type Equal<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false
Solution
首先,我們來分析一下第一個測試用例:
圖片
由上圖可知,我們需要獲取參數(shù)列表的類型和函數(shù)的返回值類型。參數(shù)列表類型要包含參數(shù)的名稱和參數(shù)的類型。那么如何獲取函數(shù)類型的參數(shù)列表類型和返回值類型呢?這時我們可以使用 TypeScript 內(nèi)置的 Parameters 和 ReturnType 工具類型。
type T0 = Parameters<() => true> // []
type T1 = Parameters<(a: string, b: number, c: boolean) => true>
// [a: string, b: number, c: boolean]
type T2 = ReturnType<() => void> // void
type T3 = ReturnType<(a: string, b: number, c: boolean) => true> // true
圖片
在以上代碼中,Parameters 工具類型用于獲取函數(shù)類型的參數(shù)列表類型,它返回的是元組類型。而 ReturnType 工具類型則用于獲取函數(shù)類型的返回值類型。它們的實現(xiàn)代碼如下所示:
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
在以上代碼中,使用了 TypeScript 條件類型和 infer 類型推斷。了解完以上代碼,我們就知道如何獲取函數(shù)類型的參數(shù)列表類型和返回值類型了。
接下來,我們要實現(xiàn)的功能就是使用函數(shù)的參數(shù)類型和返回值類型生成新的函數(shù)類型。下面我們來定義一個新的 ToCurrying 工具類型,它包含兩個類型變量 Args 和 Return,分別表示參數(shù)的類型和返回值類型:
type ToCurrying<Args extends unknown[], Return> = unknown
然后,我們來繼續(xù)分析第一個測試用例:
const curried1 = Currying((a: string, b: number, c: boolean) => true)
type cases = [
Expect<Equal< typeof curried1,
(a: string) => (b: number) => (c: boolean) => true>>
]
圖片
參考以上的圖片,我們可以總結(jié)出 ToCurrying 工具類型的處理流程:
圖片
根據(jù)上述的處理流程,我們可以利用 TypeScript 條件類型、infer 類型推斷和遞歸類型來實現(xiàn)對應(yīng)的功能:
type ToCurrying<Args extends unknown[], Return> =
Args extends [...infer Head, infer Tail]
? ToCurrying<Head, (arg: Tail) => Return>
: Return
type C0 = ToCurrying<[a: string, b: number, c: boolean], true>
// (arg: string) => (arg: number) => (arg: boolean) => true
type C1 = ToCurrying<[a: string, b: number, c: boolean, d: boolean, e: boolean,
f: string, g: boolean], true>
// (arg: string) => (arg: number) => (arg: boolean) => (arg: boolean)
// => (arg: boolean) => (arg: string) => (arg: boolean) => true
有了 ToCurrying 工具類型之后,我們來更新前面聲明的 Currying 函數(shù):
declare function Currying<T extends Function>(fn: T):
T extends (...args: infer Args) => infer Return ?
ToCurrying<Args, Return>
: never
const curried1 = Currying((a: string, b: number, c: boolean) => true)
const curried2 = Currying((a: string, b: number, c: boolean, d: boolean, e: boolean, f: string, g: boolean) => true)
const curried3 = Currying(() => true)
type cases = [
Expect<Equal<
typeof curried1,
(a: string) => (b: number) => (c: boolean) => true>>,
Expect<Equal<
typeof curried2,
(a: string) => (b: number) => (c: boolean) => (d: boolean) => (e: boolean)
=> (f: string) => (g: boolean) => true
>>,
Expect<Equal<typeof curried3, () => true>>,
]
更新后的 Currying 函數(shù),已經(jīng)可以滿足前兩個測試用例。但還不能滿足最后一個測試用例:
圖片
這是因為獲取 () => true 函數(shù)類型的參數(shù)列表類型時,返回的是空元組類型,針對這種情形,我們需要進(jìn)行對應(yīng)的處理:
declare function Currying<T extends Function>(fn: T):
T extends (...args: infer Args) => infer Return ?
Args extends []
? () => Return
: ToCurrying<Args, Return>
: never
在以上代碼中,當(dāng)發(fā)現(xiàn) Args 類型變量對應(yīng)的類型是空元組類型的話,我們直接返回 () => Return 函數(shù)類型。之后,我們就通過了所有的測試用例。最后,我們來看一下完整的代碼:
declare function Currying<T extends Function>(fn: T):
T extends (...args: infer Args) => infer Return ?
Args extends []
? () => Return
: ToCurrying<Args, Return>
: never
type ToCurrying<Args extends unknown[], Return> =
Args extends [...infer Head, infer Tail]
? ToCurrying<Head, (arg: Tail) => Return>
: Return