Hvorfor mister TypeScript typeinformasjonsherledning og går tilbake til 'any' her?
Jeg har refaktorert noen deler av React-appen vår i TypeScript og støtte på et hinder som jeg måtte jobbe rundt med, etter min mening, en veldig stygg dobbelt typeargument. Er dette en feil i TypeScript, eller er det noen typeuklarhet jeg overser som gjør at callback-parameteren standardiseres til typen 'any'?
Jeg har arbeidet rundt det ved å skjule den opprinnelige typen i et annet typeargument, som tilsynelatende fikser callback-metoden som feiltolker typen fra "extends string"-betingelsen.
Originalkode:
type Optional<TItem> = TItem extends string
? { get?: (item: TItem) => string }
: { get: (item: TItem) => string };
type TItem = { hello: string } | string;
const obj: Optional<TItem> = {
get: item => typeof item === 'string' ? item : item.hello
};
Utdata fra npx tsc for originalkode (TypeScript 5.4.2):
error TS7006: Parameter 'item' implicitly has an 'any' type.
18 get: item => typeof item === 'string' ? item : item.hello
Løsning:
type TItem = { hello: string } | string;
type Optional<TItem, TItemType> = TItemType extends string
? { get?: (item: TItem) => string }
: { get: (item: TItem) => string };
const obj: Optional<TItem, TItem> = {
get: item => typeof item === 'string' ? item : item.hello
};
Svar #1
Din opprinnelige Optional<T> er en distribuerbar betinget type, som betyr at hvis T er en union, så er også Optional<T> det. Men det blir en union av objekter, noe som betyr at get er en union av metoder, noe som betyr at parameteren item i metoden er... vel, det er ikke klart. Akkurat nå har unjoner av metoder som ikke er en del av diskriminerte unjoner ingen kontekstuell type for parameterne i metodene. Det ble laget en sak på microsoft/TypeScript#48460 som ba om bedre kontekstuell typifisering, men den ble lukket som «virker som tiltenkt». En kommentar spør om den kontekstuelle typen bør være en union, et snitt eller noe annet. Kanskje du har en mening om det, men TS-teamet synes ikke det er klart og ønsker ikke å bruke energi på å jobbe med det.
Så, hvis du er fast bestemt på å ha en sammenslåing av metoder, må du manuelt annotere parameterne og være tydelig på hvilken type du forventer at den skal ha.
Men det ser ikke ut som om du virkelig vil ha en fagforening. I stedet for
type TItem = { hello: string } | string;
du ønsker at Optional<TItem> skal evalueres til den enkelt objekt-typen {get: (item: TItem) => string}, ikke sant? Hvis så er tilfelle, er alt vi trenger å gjøre å deaktivere den distributive objekt-typen ved å bruke teknikken for å pakke det som en enkelt-element-tuple-type som er nevnt i dokumentasjonen for distributive betingede typer:
type Optional<T> = [T] extends [string]
? { get?: (item: T) => string }
: { get: (item: T) => string };
Nå vil Optional<T> aldri dele T i unionsdeler før den opererer på det. Og nå får du den kontekstuelle typen du lette etter:
const obj: Optional<TItem> = {
get: item => typeof item === 'string' ? item : item.hello // okay
};
Lenke til kode på lekeplassen
