Typescript: ReturnType of overloaded function

前端 未结 3 750
隐瞒了意图╮
隐瞒了意图╮ 2020-11-29 12:38

The type given by ReturnType seems to depend on the order the overload signatures are written

function applyChanges1(input: string): number
func         


        
相关标签:
3条回答
  • 2020-11-29 12:40

    This is a known limitation. The TypeScript team's recommendation is to include a "most general" overload signature as your last overload signature, e.g.:

    function applyChanges1(input: string): number
    function applyChanges1(input: number): string
    function applyChanges1(input: number | string): number | string
    function applyChanges1(input: number | string): number | string {
      return typeof input === "number" ? input.toString() : input.length
    }
    

    Titian Cernicova-Dragomir has a nicer alternate solution in his answer.

    0 讨论(0)
  • 2020-11-29 12:41

    As Matt McCutchen points this is a limitation of ReturnType and in general conditional types and multiple overload signatures.

    We can however construct a type that will return all overloaded return types for up to an arbitrary number of overloads:

    function applyChanges1(input: string): number
    function applyChanges1(input: number): string
    function applyChanges1(input: number | string): number | string {
    return typeof input === "number" ? input.toString() : input.length
    }
    
    function applyChanges2(input: number): string
    function applyChanges2(input: string): number
    function applyChanges2(input: number | string): number | string {
    return typeof input === "number" ? input.toString() : input.length
    }
    
    
    type OverloadedReturnType<T> = 
        T extends { (...args: any[]) : infer R; (...args: any[]) : infer R; (...args: any[]) : infer R ; (...args: any[]) : infer R } ? R  :
        T extends { (...args: any[]) : infer R; (...args: any[]) : infer R; (...args: any[]) : infer R } ? R  :
        T extends { (...args: any[]) : infer R; (...args: any[]) : infer R } ? R  :
        T extends (...args: any[]) => infer R ? R : any
    
    
    type RetO1 = OverloadedReturnType<typeof applyChanges1> // string | number 
    type RetO2 = OverloadedReturnType<typeof applyChanges2> // number | string
    

    The version above will work for up to 4 overload signatures (whatever they may be) but can easily (if not prettily) be extended to more.

    We can even get a union of possible argument types in the same way:

    type OverloadedArguments<T> = 
        T extends { (...args: infer A1) : any; (...args: infer A2) : any; (...args: infer A3) : any ; (...args: infer A4) : any } ? A1|A2|A3|A4  :
        T extends { (...args: infer A1) : any; (...args: infer A2) : any; (...args: infer A3) : any } ? A1|A2|A3 :
        T extends { (...args: infer A1) : any; (...args: infer A2) : any } ? A1|A2  :
        T extends (...args: infer A) => any ? A : any
    
    
    type RetO1 = OverloadedArguments<typeof applyChanges1> // [string] & [number]
    type RetO2 = OverloadedArguments<typeof applyChanges2>  // [number] & [string]
    
    0 讨论(0)
  • 2020-11-29 12:57

    I had a similar problem -- I needed to pick the ReturnType of the exact overload, based on the arguments I have.

    e.g.:

    function applyChanges1(input: string): number
    function applyChanges1(input: number): string
    function applyChanges1(input: boolean): object
    function applyChanges1(input: number | string | boolean): number | string | object {
        return typeof input === "number" ? input.toString()
             : typeof input === "boolean" ? { input }
             : input.length;
    }
    
    
    // Needed:
    
    type Ret11 = ReturnTypeWithArgs<typeof applyChanges1, [string]> // number
    type Ret12 = ReturnTypeWithArgs<typeof applyChanges1, [number]> // string
    type Ret13 = ReturnTypeWithArgs<typeof applyChanges1, [boolean]> // object
    type Ret14 = ReturnTypeWithArgs<typeof applyChanges1, [number | string]> // number | string
    type Ret15 = ReturnTypeWithArgs<typeof applyChanges1, [number | boolean]> // string | object
    type Ret16 = ReturnTypeWithArgs<typeof applyChanges1, [number | string | boolean]> // number | string | object
    

    So I created the following ReturnTypeWithArgs utility based on the beautiful answer of @Titian above and using Extract.

    type ReturnTypeWithArgs<T extends (...args: any[]) => any, ARGS_T> =
        Extract<
            T extends { (...args: infer A1): infer R1; (...args: infer A2): infer R2; (...args: infer A3): infer R3; (...args: infer A4): infer R4; } ? [A1, R1] | [A2, R2] | [A3, R3] | [A4, R4] :
            T extends { (...args: infer A1): infer R1; (...args: infer A2): infer R2; (...args: infer A3): infer R3; } ? [A1, R1] | [A2, R2] | [A3, R3] :
            T extends { (...args: infer A1): infer R1; (...args: infer A2): infer R2; } ? [A1, R1] | [A2, R2] :
            T extends { (...args: infer A1): infer R1; } ? [A1, R1] :
            never,
            [ARGS_T, any]
        >[1]
    

    It works like a charm!

    Playground Link


    PS. In my real case I have 7 overloads, so wish me luck! ;D

    0 讨论(0)
提交回复
热议问题