Typescript: Skicka en unionstyp till en överbelastad funktion?
Jag har den här klassen:
type Params = {
data: string | string[]
}
class Test {
data: string | string[]
constructor(params: Params) {
this.data = this.initData(params.data)
}
initData(data: string): string
initData(data: string[]): string[]
initData(data: string | string[]): string | string[] {
if (typeof data === 'string') {
return data as string
} else {
return data as string[]
}
}
}
Men jag får detta fel:
No overload matches this call.
Overload 1 of 2, '(data: string): string', gave the following error.
Argument of type 'string | string[]' is not assignable to parameter of type 'string'.
Type 'string[]' is not assignable to type 'string'.
Overload 2 of 2, '(data: string[]): string[]', gave the following error.
Argument of type 'string | string[]' is not assignable to parameter of type 'string[]'.
Type 'string' is not assignable to type 'string[]'.ts(2769)
Untitled-1(38, 5): The call would have succeeded against this implementation, but implementation signatures of overloads are not externally visible.
Jag hittade en dum tillfällig lösning som hjälper:
constructor(params: Params) {
this.data = this.initData(params.data)
if (typeof params.data === 'string') {
this.data = this.initData(params.data)
}
else {
this.data = this.initData(params.data)
}
}
Och jag förstår på ett sätt varför det här fungerar, men kanske finns det ett bättre sätt att lösa det här?
Svar #1
Det finns en långvarig öppen funktionförfrågan på microsoft/TypeScript#14107 för att stödja att anropa en överlagrad funktion med en lämplig union av dess argument. Det vill säga att distribuera överlagringsupplösning över unioner av argumenttyper. För närvarande är det inte en del av språket och det finns bara kringlösningar.
Om du vill anropa en överlagrad funktion idag måste du se till att anropet är lämpligt för åtminstone en av dess anropssignaturer. Så den vanligaste lösningen är att lägga till den saknade anropssignaturen som motsvarar ditt misslyckade anrop:
class Test {
data: string | string[]
constructor(params: Params) {
this.data = this.initData(params.data)
}
// call signatures
initData(data: string): string
initData(data: string[]): string[]
initData(data: string | string[]): string | string[]; // <-- add this
// implementation
initData(data: string | string[]): string | string[] {
return data
}
}
Det är tillräckligt enkelt att göra här eftersom du bara behöver lägga till en extra anropssignatur. Men om du ursprungligen hade 𝒏-anropssignaturer, skulle kombinationen av alla möjliga samlingar av dem resultera i något i storleksordningen 2𝒏 anropssignaturer. Den växer mycket snabbt med 𝒏. För att undvika den exponentiella tillväxten kan du ha en enda generisk anropssignatur som använder en distributiv villkorlig typ för att beräkna returtypen:
class Test {
data: string | string[]
constructor(params: Params) {
this.data = this.initData(params.data)
}
// call signature
initData<T extends string | string[]>(data: T):
T extends string ? string :
T extends string[] ? string[] :
never;
// implementation
initData(data: string | string[]): string | string[] {
return data
}
}
Nu kan den enskilda anropssignaturen automatiskt distribueras över unioner och den skalar linjärt med antalet ursprungliga anropssignaturer. Den är ändå implementerad som en överlagring, eftersom kompilatorn för närvarande inte kan verifiera kropparna av funktioner som returnerar generiska villkorstyper. Se microsoft/TypeScript#33912 för en diskussion om det. Så vi använder "lösheten" i implementeringen av överlagrade funktioner för att undvika typexpressioner här.
Playground-länk till kod
