Typescript generisk unionstype
Jeg prøver å lage en enkel switch-funksjon som tar en første parameter som må være en forening av streng og et objekt som har nøkler basert på den første parameterens forening og kan returnere hvilken som helst verdi.
export const mySwitch = <T extends string>(value: T, possibilities: {[key in T]: any}): any => {
return possibilities[value];
};
Typisk bruk vil være
let option: "val1" | "val2" | "val3" = "val1";
// should returns s1
// Impossible should be type-checked as an error since it's not part of the option union type
mySwitch(option, {val1: "s1", val2: "s2", val3: "s3", impossible: "impossible"});
Problemet mitt oppstår fordi generisk type T må være en string for å kunne brukes som en objekt-nøkkel. Jeg vet ikke hvordan du kan si til T å være en union av string.
Jeg prøvde T extends string uten suksess.
Svar #1
Versjonen T extends string ser ut til å fungere bra. Den tillater ikke impossible, men ville du ikke ønsket å ikke tillate det siden hvis parameteren aldri kan ha den verdien, ville det alternativet være ubrukelig?
export const mySwitch = <T extends string>(value: T, possibilities: {[key in T]: any}): any => {
return possibilities[value];
};
declare let option: "val1" | "val2" | "val3";
mySwitch(option, {val1: "s1", val2: "s2", val3: "s3", impossible: "impossible"});
spille
Hvis du vil tillate de ekstra nøklene, kan du erklære case-objektet separat (og omgå kontroller for overskytende egenskaper og la deg gjenbruke case-objektet)
declare let option: "val1" | "val2" | "val3";
const casses = {val1: "s1", val2: "s2", val3: "s3", impossible: "impossible"}
mySwitch(option, casses);
spille
Eller du kan endre typen din litt slik at den generiske typeparameteren er case-objektet, og verdien vil da bli typet som keyof T:
export const mySwitch = <T>(value: keyof T, possibilities: T): any => {
return possibilities[value];
};
declare let option: "val1" | "val2" | "val3";
mySwitch(option, {val1: "s1", val2: "s2", val3: "s3", impossible: "impossible"});
spille
Et bedre alternativ vil også være å bevare typen fra case-objektet i stedet for å bruke any:
export const mySwitch = <T, K extends keyof T>(value: K, possibilities: T): T[K] => {
return possibilities[value];
};
declare let option: "val1" | "val2" | "val3";
mySwitch(option, {val1: 1, val2: "s2", val3: "s3", impossible: false}); // returns string | number
spille
Rediger:
For å bevare både korrekt returtype og feil hvis det finnes muligheter som ikke er til stede i unionen, kan du bruke dette:
const mySwitch = <T extends Record<K, any>, K extends string>(value: K, possibilities: T & Record<Exclude<keyof T, K>, never>): any => {
return possibilities[value];
};
let option: "val1" | "val2" | "val3" = (["val1", "val2", "val3"] as const)[Math.round(Math.random() * 2)]
mySwitch(option, {val1: "s1", val2: "s2", val3: "s3" });
mySwitch(option, {val1: "s1", val2: "s2", val3: "s3", impossible: "" }); //err on impossible
spille
Merk at fordi TypeScript gjør kontrollflytanalyse, må du sørge for at option ikke bare er typen som den faktiske konstanten du tildeler i stedet for typeannotasjonen du spesifiserer
