Typescript mappad returtyp: snitt reducerat till aldrig, motsägande snittegenskap
Mål: göra returtypen för en funktion baserad på indatatypen med hjälp av en mappad typ för att slå upp returtypen.
Problem: Jag får ett fel på grund av konflikter i snittet av diskriminerade unionstyper, vilket jag konceptuellt förstår men jag vet inte hur jag ska strukturera mina typer för att uppnå mitt mål.
Type 'Integration' is not assignable to type 'IntegrationTypeData[T]'.
Type 'IntegrationA' is not assignable to type 'IntegrationTypeData[T]'.
Type 'IntegrationA' is not assignable to type 'never'.
The intersection 'IntegrationA & IntegrationB' was reduced to 'never' because property 'name' has conflicting types in some constituents.
Min typ ser ut som följande:
enum Integrations {
A = 'A',
B = 'B',
}
type IntegrationMap<M extends { [key: string]: any }> = {
[Key in keyof M]: M[Key]
};
type IntegrationA = {
name: Integrations.A,
propertyA: string;
}
type IntegrationB = {
name: Integrations.B,
propertyB: number;
}
type IntegrationData = {
[Integrations.A]: IntegrationA;
[Integrations.B]: IntegrationB;
};
// this resolves to type Integration = IntegrationA | IntegrationB
type Integration = IntegrationMap<IntegrationData>[keyof IntegrationMap<IntegrationData>];
// the function I want to make have a dynamic/mapped return type
const getIntegration = <T extends Integrations>(name: T): IntegrationData[T] => {
// the type of the integrations variable is Integration[]
const integration = integrations.find((i: Integration) => i.name === name);
return integration;
};
// desired usage
// typeof intB = IntegrationB
const intB = getIntegration(Integrations.B);
Kan någon hjälpa mig att förstå hur man löser detta fel och hur man korrekt skriver datan så att jag får en dynamisk och typ-säker returtyp av denna funktion?
Svar #1
Elementen i integrations-arrayen är av unionstypen (IntegrationA | IntegrationB).
Om du tittar på standardanropssignaturen för find()-metoden för arrayer, kommer du att se att den returnerar ett värde av antingen elementtypen (IntegrationA | IntegrationB i detta fall) eller undefined. Så du returnerar ett värde av typen IntegrationA | IntegrationB | undefined. Men eftersom returtypen för din funktion är IntegrationData[T] där T är typen av name-argumentet som skickas in, kan inte kompilatorn vara säker på att det förra kan tilldelas det senare, så den ger ett fel.
Det specifika felmeddelandet du får handlar om snitt-typen IntegrationA & IntegrationB som reduceras till typen never eftersom det enda sättet att vara säker på att du returnerar en Integration[T] oberoende av T är om ditt värde vore både en IntegrationA och en IntegrationB, vilket är omöjligt. Det är förmodligen bäst att inte fastna för mycket i felmeddelandet.
Det räcker att säga att problemet här är att find():s returtyp är för bred för dina behov.
Som tur är finns det en annan anropssignatur för find() som har potential att returnera en smalare typ än arrayelementtypen (men ändå möjligen undefined). Om callbacken du skickar in är en användardefinierad typvaktfunktion med en returtypspredikat av formen x is Y, så kommer den att returnera den smalare typen Y | undefined.
Det vore trevligt om kompilatorn kunde förstå att i => i.name === name fungerar som en typvaktfunktion med anropssignatur (i: Integration) => i är IntegrationData[T]. Tyvärr händer detta inte; TypeScript härleder inte typvaktfunktionsanropssignaturer från implementationer. Det finns en öppen begäran på microsoft/TypeScript#38390 om att stödja detta i vissa fall, men för tillfället är det inte en del av språket. (Och även om det vore det, kan sådan härledning i denna situation ligga utanför dess förmåga.)
Om du vill använda i => i.name === name som en typvaktfunktion behöver du annotera den som sådan:
const getIntegration = <T extends Integrations>(name: T): IntegrationData[T] => {
const integration = integrations.find(
(i: Integration): i is IntegrationData[T] => i.name === name
// annotated ---> ^^^^^^^^^^^^^^^^^^^^^^^^^
);
return integration!;
};
Annoteringen tar dig nästan hela vägen. Den eliminerar dock fortfarande inte undefined. För allt kompilatorn vet, har integrations faktiskt inget element av typen IntegrationData[T] i sig. Så integration kan vara undefined. Du kan hantera detta explicit genom att skriva if (!integration) throw new Error("OH NO I WAS WRONG") innan return. Eller, om du är säker på att det faktiskt inte kommer att vara undefined, kan du meddela detta till kompilatorn genom att använda en icke-null-assertionsoperator (!). Genom att skriva return integration! hävdar du att integration inte kommer att vara undefined eller null, och därmed kan värdet av typen IntegrationData[T] | undefined behandlas som IntegrationData[T].
Och nu kompilerar det utan fel.
Playground-länk till kod
