typescript - Blanding av unionstyper, generiske typer og betingede typer forårsaker uventet «Type kan ikke tildeles til typen»-feil
Jeg har støtt på et problem med typebestemmelse, spesielt når betingede typer brukes innenfor unionstyper.
Det kan finnes en kortere måte å demonstrere dette problemet på, men jeg kunne ikke finne en...
Se problemet i aksjon på denne lekeplasslenken.
Vurder følgende Result<T>, en unionstype brukt for å indikere suksess eller feil ved en operasjon (med en valgfri tilknyttet verdi, av typen T). For suksess-tilfellet har jeg brukt den betingede typen SuccessResult<T>, som løser seg til enten OKResult eller ValueResult<T> (avhengig av om resultatet også skal ha en tilknyttet value:
type Result<T = undefined> = SuccessResult<T> | ErrorResult;
interface OKResult {
type: 'OK';
}
interface ValueResult<T> {
type: 'OK';
value: T;
}
interface ErrorResult {
type: 'Error';
error: any;
}
type SuccessResult<T = undefined> = T extends undefined ? OKResult : ValueResult<T>;
function isSuccess<T>(result: Result<T>): result is SuccessResult<T> {
return result.type === 'OK';
}
La oss bruke det med en enkel unionstype:
type C1 = "A1" | "B1";
function makeC1(): C1 { return "A1" }
const c1: C1 = makeC1();
const c1Result: Result<C1> = { type: "OK", value: c1 }; // ALL IS GOOD
Nå, i stedet for den enkle unionstypen C1, som bare er "A1" | "B1", la oss bruke en unionstype av komplekse verdier, C2, på nøyaktig samme måte:
type A2 = {
type: 'A2';
}
type B2 = {
type: 'B2';
}
type C2 = A2 | B2;
function makeC2(): C2 { return { type: "A2" } }
const c2: C2 = makeC2();
const c2Result: Result<C2> = { type: "OK", value: c2 }; // OH DEAR!
Dette resulterer i en feil:
Type 'C2' kan ikke tildeles typen 'B2'.
Type 'A2' kan ikke tildeles typen 'B2'.
Typer av egenskap 'type' er inkompatible.
Type '"A2"' kan ikke tildeles typen '"B2"'.
Hvis jeg fjerner betinget typisering fra ligningen og definerer min Result<T> til å bruke ValueResult<T> i stedet for SuccessResult<T>:
type Result<T = undefined> = ValueResult<T> | ErrorResult;
...alt fungerer igjen, men jeg mister evnen til å signalisere verdiløs suksess. Dette ville være en trist tilbakefall hvis jeg ikke får den valgfrie typingen til å fungere i dette tilfellet.
Hvor gikk jeg galt? Hvordan kan jeg bruke SuccessResult<T> i Result<T>-unionen, hvor T i seg selv er en kompleks unionstype?
Lekerom lenke
Svar #1
Dette skjer fordi C2 er av typen A2 | B2, mens Result<C2> er {type: 'OK'; value: A2} | {type: 'OK'; value: B2} | ErrorResult.
Så problemet koker ned til ekvivalensen mellom {type: 'OK'; value: A2 | B2} og {type: 'OK'; value: A2} | {type: 'OK'; value: B2}. Dette virker ekvivalent inntil du legger til flere egenskaper i et slikt objekt. TypeScript behandler ikke dette som en spesiell sak og ser på det generelle tilfellet (tenk deg flere egenskaper) som ikke er ekvivalent.
La oss vurdere et annet eksempel: { x: "foo" | "bar" } og { x: "foo" } | { x: "bar" }.
For eksempel ville det ikke være korrekt å betrakte
{ x: "foo" | "bar", y: string | number }som ekvivalent med{ x: "foo", y: string } | { x: "bar", y: number }fordi den første formen tillater alle fire kombinasjonene, mens den andre formen kun tillater to spesifikke.
Kilde: https://github.com/microsoft/TypeScript/issues/12052#issuecomment-258653766
Svar #2
type Result<T = undefined> = SuccessResult<T> | ErrorResult;må være
type Result<T = undefined> = SuccessResult<T> | ErrorResult | ValueResult<T>;så kompilerer det.
Skål, Mike