Typescript: Sende en unionstype til en overlasta funksjon?
Jeg har denne 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 jeg får denne feilen:
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.
Jeg fant en dum løsning som hjelper:
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)
}
}
Og jeg skjønner på en måte hvorfor dette fungerer, men kanskje det finnes en bedre måte å løse dette på?
Svar #1
Det finnes en langvarig åpen funksjonsforespørsel på microsoft/TypeScript#14107 for å støtte å kalle en overbelastet funksjon med en passende union av dens argumenter. Det vil si, å distribuere overbelastningsresolusjon over unjoner av argumenttyper. Foreløpig er det ikke en del av språket, og det finnes bare arbeidsomveier.
Hvis du vil kalle en overbelastet funksjon i dag, må du sørge for at kallet er passende for minst én av dens kallesignaturer. Den vanligste løsningen er derfor å legge til den manglende kallesignaturen som tilsvarer ditt mislykkede kall:
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 er ganske enkelt å gjøre her fordi du bare trenger å legge til én ekstra anropssignatur. Men hvis du hadde 𝒏-anropssignaturer fra starten, ville en kombinasjon av alle mulige samlinger av dem resultere i noe i størrelsesorden 2𝒏 anropssignaturer. Den vokser veldig raskt med 𝒏. For å unngå eksponentiell vekst, kan du bare ha en enkelt generisk anropssignatur som bruker en distribuerbar betinget type for å beregne 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
}
}
Nå kan den enkeltstående kallsignaturen automatisk distribuere over foreninger, og den skalerer lineært med antall opprinnelige kallsignaturer. Den er fortsatt implementert som en overbelastning, fordi kompilatoren for øyeblikket ikke er i stand til å verifisere kroppene til funksjoner som returnerer generiske betingede typer. Se microsoft/TypeScript#33912 for en diskusjon om det. Så vi bruker "løsningen" i implementeringen av overbelastede funksjoner for å unngå typepåstander her.
Lenke til kode på lekeplassen
