Typescript: ReturnType av överlagrad funktion


Typen som ges av ReturnType verkar bero på i vilken ordning överlagringssignaturerna är skrivna

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 Ret1 = ReturnType<typeof applyChanges1> // string
type Ret2 = ReturnType<typeof applyChanges2> // number

Det verkar ta returtypen från den sista överlagrade signaturen, vilket verkar ganska godtyckligt. Jag förväntade mig att både Ret1 och Ret2 skulle vara string | number. Finns det en anledning till detta beteende?

Svar #1

Som Matt McCutchen påpekar är detta en begränsning av ReturnType och i allmänhet villkorliga typer och flera överlagringssignaturer.

Vi kan dock konstruera en typ som kommer att returnera alla överlagrade returtyper för upp till ett godtyckligt antal överlagringar:

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

Lekplatslänk

Versionen ovan fungerar för upp till 4 överlastsignaturer (oavsett vad de kan vara) men kan enkelt (om än inte snyggt) utökas till fler.

Vi kan till och med få en union av möjliga argumenttyper på samma sätt:

type OverloadedParameters<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 Params01 = OverloadedParameters<typeof applyChanges1> // [string] | [number]
type Params02 = OverloadedParameters<typeof applyChanges2>  // [number] | [string]

Lekplatslänk

Svar #2

Jag hade ett liknande problem -- jag behövde välja ReturnType för den exakta överlagringen, baserat på de argument jag har.

t.ex.:

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

Så jag skapade följande ReturnTypeWithArgs-verktyg baserat på det vackra svaret från @Titian ovan och använde 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]

Det fungerar som en charm!

Lekplatslänk

PS. I mitt verkliga fall har jag 7 överlagringar, så önska mig lycka till! ;D

Relaterade artiklar