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
