typescript - Hvorfor endrer det seg hvordan en generisk type blir inferert når man legger til en begrensning?
Vurder det følgende eksempelet:
function identity<T>(arr: T[]) { return arr }
identity(["a", "b"])
Her blir den generiske typen T antatt å være en string, noe som gir mening for meg.
Men hvis en begrensning legges til på T (som vist nedenfor), blir T da utledet som "a" | "b", hvorfor?
function identity<T extends string>(arr: T[]) { return arr }
identity(["a", "b"])
Svar #1
Jeg har lært noen ting å dele. Det er ikke et definitivt svar, men verdt å merke seg:
Når blir en typeparameter utvidet, fra "a" til string?
https://github.com/microsoft/TypeScript/pull/10676
Under type-argumentslutning for et kalluttrykk blir typen som sluttes for en typeparameter T utvidet til dens utvidede litteraltyper hvis:
- alle slutninger for T ble gjort til toppnivåforekomster av T innenfor den aktuelle parametertypen, og
- T har ingen begrensning eller begrensningen inkluderer ikke primitive eller litterale typer, og
- T var fast under inferens eller T forekommer ikke på toppnivå i returtypen.
Typeparameter T utvides hvis:
Thar ingen begrensning- eller dens begrensning inkluderer ikke primitive eller bokstavelige typer
Utvidet
<T>(arr:T[]) har ingen begrensning, så typene vil bli utvidet. 'a'=string
Den ukonstruerte versjonen er så generell at TS ikke gjør for mange antakelser om hvordan man skal behandle litteraler.
Ikke utvidet
<T extends string>(arr:T[]) har en primitiv begrensning og blir ikke utvidet.
Siden den begrensede versjonen er mindre generell, bevarer den dem. Se PR for generelt å ønske å bevare litteraler så mye som mulig.
Dette er også en interessant liten detalj:
I tilfeller der bokstavelige typer beholdes i slutninger for en typeparameter (dvs. når ingen utvidelse finner sted), hvis alle slutninger er bokstavelige typer eller bokstavelige unionstyper med samme grunnleggende primitive type, er den resulterende infererte typen en union av slutningene. Ellers er den infererte typen den felles supertypen til slutningene, og en feil oppstår hvis det ikke finnes en slik felles supertype.
Alle inferenser av T er litterale typer ("a", "b") av samme grunnleggende primitive type (string).
const fn = <T extends string>(arr:T[]) => arr
fn(['a', 'b']) // ('a' | 'b')[]
Hvis inferensen ikke var en bokstavelig type, er den infererte typen den felles supertypen, string.
const fn = <T extends string>(arr:T[]) => arr
fn(['a' as string, "b"]) // widened to common supertype `string`
Generelt sett vil TS antyde den "beste" typen for en gitt input. Denne funksjonen tilfeldigvis sender strengliteraler (som et uforanderlig uttrykk) til identity, hvis returtype er T[] hvor T er begrenset av string. Hvis det kan bevare literaler, vil det gjøre det.
Du kan se at det finnes et hav av scenarier (limt inn fra den PR-en) hvor inferens kan bevare literals, og hvor det ikke kan.
- https://github.com/microsoft/TypeScript/pull/10676
- https://github.com/microsoft/TypeScript/pull/11126
Jeg har ikke et svar på hvilken kombinasjon av slutningsregler som spesifikt muliggjør denne oppførselen, men generelt vil TypeScript være TypeScript, og generelt prøve å produsere resultatet vi ønsker.
