typer - Hvordan overbelaste funksjoner korrekt i TypeScript?
Kan noen være så snill å fortelle meg hva problemet med typene her er? Jeg prøver å gjøre meg forstått med overbelastninger i TS, men problemet nedenfor gjør meg virkelig forvirret...
type myNumbers = 1 | 2 | 3
type myStrings = 'a' | 'b' | 'c';
interface ReturnValue {
(tag: 'a'): 1
(tag: 'b'): 2
(tag: 'c'): 3
(tag: myStrings): myNumbers
}
class Log {
public returnValue: ReturnValue = (tag: myStrings): myNumbers => {
switch (tag) {
case 'a':
return 1;
case 'b':
return 2;
case 'c':
return 3;
}
}
}
Type '(tag: myStrings) => myNumbers' is not assignable to type 'ReturnValue'.
Type 'myNumbers' is not assignable to type '1'.
Type '2' is not assignable to type '1'.
Svar #1
Overbelastninger i TypeScript består av flere kallesignaturer og en enkelt implementering, som har sin egen signatur. TypeScript typekontrollerer aldri egentlig implementeringen mot kallesignaturene på en «korrekt» måte. Det sjekkes enten for løst og tillater uriktige implementeringer, eller for strengt og hindrer riktige implementeringer. Så det finnes ingen måte å «korrekt» overbelaste funksjoner på i TypeScript. I stedet må du velge en retning å feile i.
Hvis du skriver en overbelastet function-erklæring eller en class-metode, vil kompilatoren sjekke implementeringen løst. Dette gjør det enkelt å skrive overbelastninger uten kompilatorfeil:
function returnValue(tag: "a"): 1;
function returnValue(tag: "b"): 2;
function returnValue(tag: "c"): 3;
function returnValue(tag: MyStrings): MyNumbers;
function returnValue(tag: MyStrings) {
switch (tag) {
case 'a': return 1;
case 'b': return 2;
case 'c': return 3;
}
}
class Log {
public returnValue: ReturnValue = returnValue;
}
eller
class Log {
public returnValue(tag: "a"): 1;
public returnValue(tag: "b"): 2;
public returnValue(tag: "c"): 3;
public returnValue(tag: MyStrings): MyNumbers;
public returnValue(tag: MyStrings) {
switch (tag) {
case 'a': return 1;
case 'b': return 2;
case 'c': return 3;
}
}
}
Så det er en tilnærming du kan ta: endre funksjonsuttrykket ditt til en funksjonserklæring eller en klassemethode.
Men den løsheten fanger bare de mest åpenbare feilene der implementeringssignaturen definitivt er inkompatibel med kall-signaturene. Den vil ikke fange situasjoner der du blander hvilken returtype som hører til hvilken kall-signatur:
function returnValue(tag: "a"): 1;
function returnValue(tag: "b"): 2;
function returnValue(tag: "c"): 3;
function returnValue(tag: MyStrings): MyNumbers;
function returnValue(tag: MyStrings) {
switch (tag) {
case 'a': return 3; // oops, but no error
case 'b': return 1; // oops, but no error
case 'c': return 2; // oops, but no error
}
}
På en måte bryr funksjonserklæring og klassemetodeoverbelastninger seg bare om at implementeringen returnerer foreningen av returtypene til kallsignaturen.
Så husk å sjekke implementeringene dine nøye hvis du bruker slike overbelastninger, fordi kompilatoren ikke kan
På den annen side, hvis du prøver å skrive et overbelastet function-uttrykk eller pilfunksjon, vil kompilatoren bare typekontrollere hvis implementasjonens returtype kan tildeles til alle returtypene til kallesignaturen, det vil si deres snitt.
const ret: ReturnValue = (tag: MyStrings) => {
throw new Error();
} // okay
const slightlyLessContrived: {
(x: string): { a: string };
(x: number): { b: number };
} = (x: string | number) => ({ a: "", b: 1 }); // okay
Den første linjen kompilerer fordi implementeringen av ret returnerer never-typen, som er det du får når du prøver å si at en verdi er 1 & 2 & 3 på en gang... det vil si, det er umulig. Og den andre linjen kompilerer fordi implementeringen returnerer {a: string, b: number}, som er ekvivalent med {a: string} & {b: number}.
Men slike implementeringer er ganske ubrukelige; hvis du var villig til å returnere skjæringspunktet av returtypene, ville du ikke trengt overbelastninger i utgangspunktet. Du ville bare brukt implementeringen direkte.
Det finnes for øyeblikket ingen mulighet til å skrive et funksjonsuttrykk og få løs typekontroll fra funksjonsuttalelser. Det finnes en åpen funksjonsforespørsel på microsoft/TypeScript#47669 for det, men så langt er det ikke en del av språket.
Men hvis du vil løsne typetesting av et funksjonsuttrykk, kan du gjøre det på samme måte som du løsner typetesting for ethvert uttrykk: bruk en typepåstand:
class Log {
public returnValue = ((tag: MyStrings): MyNumbers => {
switch (tag) {
case 'a': return 1;
case 'b': return 2;
case 'c': return 3;
}
}) as ReturnValue; // okay
}
Men dette har det samme problemet som før når det gjelder å tillate usikre implementeringer:
class Log {
public returnValue = ((tag: MyStrings): MyNumbers => {
switch (tag) {
case 'a': return 3; // oops
case 'b': return 1; // I
case 'c': return 2; // messed up
}
}) as ReturnValue; // still okay
}
Igjen, du må være forsiktig.
Så det er valgene dine hvis du vil skrive overbelastninger: enten skriv en funksjonsdeklarasjon eller klassemetode, eller skriv et funksjonsuttrykk med en typepåstand. Om dette er "korrekt" eller ikke, er derimot subjektivt.
Lenke til kode på lekeplassen
