typescript - Hvordan lage en betinget generisk type som har en verdi når den er undefined, men ikke når den er undefined OG en annen ting


Jeg har en type der jeg ønsker å definere en objekt-nøkkel som valgfri med any hvis jeg ikke gir noen verdi

type OptionalData<T> = T extends undefined ? { data?: any } : { data: T };

export type DataObject<T = any> = {
  item?: string | null;
} & OptionalData<T>;

Det fungerer greit helt til jeg setter min generiske som something | undefined

const data: DataObject<string | undefined> = { data: true }; // WRONG!
const data2: DataObject<string | number> = { data: true }; // CORRECT

siden min generiske også kan utvide undefined, får jeg data som valgfri any

Finnes det en måte å si «det er bare valgfritt any når det KUN kan utvides med undefined» eller noe sånt?

Lekeplass

Svar #1

Problem

Du har disse typene:

type OptionalData<T> = T extends undefined ? { data?: any } : { data: T };

export type DataObject<T = any> = {
  item?: string | null;
} & OptionalData<T>;

Når du skriver en variabel som DataObject med typeparameteren som er en union av en type (slik som string) og undefined, forventer du at data-egenskapen skal ha unionstypen, men den blir typet som any:

const data: DataObject<string | undefined> = { data: ... }

// data.data should be typed as `string | undefined` however
// data.data is typed as `any`

Hva skjer?

TypeScripts dokumentasjon for betingede typer er nyttig her. Den lyder:

Betingede typer der den kontrollerte typen er en naken typeparameter kalles distribuerbare betingede typer. Distribuerbare betingede typer distribueres automatisk over unionstyper under instansiering. For eksempel, en instansiering av T extends U ? X : Y med typeargumentet A | B | C for T blir løst som (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y).

- Dokumentasjon om distribuerbare betingede typer

Så i ditt tilfelle løser det seg til

| (string extends undefined ? { data?: any } : { data: T })
| (undefined extends undefined ? { data?: any } : { data: T })

Den første betingede typen løser seg til { data: T } og den andre løser seg til { data?: any }. Jeg tror foreningstyper vil løse seg til typen som er mest generell, så typen blir til slutt { data?: any }.

Løsning

Her er OptionalData oppdatert slik at det fungerer som forventet:

type OptionalData<T> = [T] extends [undefined] ? { data?: any } : { data: T };

Vi pakket nettopp inn T og undefined med hakeparenteser ([]), og gjorde dem om til tupler (eller teknisk sett monuple fordi den bare har ett element). Her er demonstrasjonen.

Hvorfor fungerer dette?

La oss se tilbake på dokumentasjonen for betingede typer igjen, med litt ekstra vekt på de viktige delene:

Betingede typer der den sjekket typen er en naken type-parameter kalles distributive betingede typer. Distributive betingede typer blir automatisk distribuert over unionstyper under instansiering.

Når vi gjorde de nakne typene om til tuple, er de ikke nakne lenger. Og fordi de ikke lenger er nakne, er de ikke lenger distribuert.

Relaterte artikler