Hur kan jag casta eller ändra den generiska typen i TypeScript?
function cast<R, T>(v: T): R {
return v as R;
~~~~~~
// Error
}
Så det jag försöker göra är att ändra typen på v från T till R. Men, jag ser följande fel.
Conversion of type 'T' to type 'R' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
'T' is assignable to the constraint of type 'R', but 'R' could be instantiated with a different subtype of constraint '{}'.
Och att använda InstanceType<typeof R> ger mig också ett fel. Är det någon som vet hur man gör detta?
Svar #1
TypeScripts typförsäkringar, i formen expr as Type, låter dig tala om för kompilatorn att ett uttryck expr är av en viss typ Type. Kompilatorn gör vanligtvis ett ganska bra jobb med att lista ut vilka typer uttryck borde ha, men ibland vet du som utvecklare något som kompilatorn inte vet, och detta är hur du talar om det för kompilatorn.
Observera att expr as Type inte har någon som helst effekt vid körning. För det mesta beskriver de typer som förekommer i TypeScript bara vad som förväntas hända vid körning; de ändrar inte vad som faktiskt händer. Det statiska typesystemet raderas i princip från den JavaScript som faktiskt körs.
Så, expr as number är en kompileringstid typförsäkran, inte en körningstid typomvandling. Det utför till exempel inte typkonvertering som 0+expr eller typomvandling som Number(expr).
Okej, en typförsäkran låter oss tala om för kompilatorn vilken typ den ska betrakta uttrycket som. Men kompilatorn accepterar inte automatiskt alla sådana försäkringar utan invändning. Generellt kommer den att tillåta försäkran om den redan anser att uttrycket kan tilldelas den typen:
const foo = "foo" as string; // okay, widened from "foo" to string
som du kan tänka på som "uppkastning" men som kanske bättre kan beskrivas som "vidgning". Det kommer också att tillåta påståendet om det tror att uttrycket kan vara den typen, eftersom den påstådda typen kan tilldelas den:
let bar = (Math.random() < 99 ? "bar" : 1) as string;
// okay, narrowed from string | number to string
som du kan tänka på som "nedklassning" men som kanske är bättre beskrivet som "förminskning". Dessa regler är inte hela historien, men den allmänna idén är att kompilatorn tillåter påståenden om de är vidgande eller förminskande.
Kompilatorn tvekar inför ett påstående som den inte uppfattar som vare sig en utvidgning eller en inskränkning. Om den tror att du tar ett uttryck av typen X och påstår att det är av typen Y, och om varken X eller Y kan tilldelas den andra, så "överlappas" inte typerna tillräckligt och du kanske gör typ-påståendet felaktigt. Detta gäller de två obestämda generiska typparametrarna T och R. Om T extends R eller R extends T var sant, skulle det vara okej, men som det är nu kan de vara helt orelaterade typer, så påståendet blockeras:
function castBad<R, T>(v: T): R {
return v as R; // error!
// --> ~~~~~~
// Conversion of type 'T' to type 'R' may be a mistake because
// neither type sufficiently overlaps with the other.
// If this was intentional, convert the expression to 'unknown' first.
}
Felmeddelandet ger dig ledtråden du behöver för att lyckas med detta: gör en mellanliggande typkonvertering till något antingen så brett eller så smalt att kompilatorn ser varje steg som relaterat. Alla typer kan tilldelas typen unknown, så du kan använda den som en mellanliggande:
function cast<R, T>(v: T): R {
return v as unknown as R; // okay
}
Med v as unknown vidgar du T till unknown, och sedan med (v as unknown) as R begränsar du unknown till R. Du kan också använda någon av följande:
v as never as R; // narrow to never, widen back to R
v as any as R; // narrow/widen to any, narrow/widen back to R
v as (T | R) as R; // widen to the union, narrow back to the other member
v as (T & R) as R; // narrow to the intersection, widen back to the other member
Så, det borde fungera. Jag är orolig över användningsfallet för en funktion som cast(), eftersom det bara är en typförsäkran klädd som en funktion, och därför har samma varningar om att det egentligen inte är vad folk vanligtvis menar med "cast":
const x = cast<number, string>("hello");
console.log(x.toFixed(1)); // okay at compile time, error at runtime. oops
Dessutom, om du verkligen vill ha en typförsäkringsfunktion, gör inte den generiska parametern T mycket tungt arbete; den måste anges manuellt men skulle rimligen kunna härledas från argumentet du skickar in. Och faktiskt, eftersom den bara används för typen av argumentet, kan du helt enkelt ta bort den helt:
function typeAssert<R>(v: any): R {
return v;
}
Och sedan, när du använder det, behöver du bara specificera den önskade returtypen:
const y = typeAssert<string>(Math.random() < 99 ? "okay" : 123); // string
Playground-länk till kod
