Hvordan forhindre at `Required` i TypeScript fjerner `undefined` fra typen når man bruker --strictNullChecks


Typescript lar deg fjerne 'optionality' (?) med -? mapped type-modifikatoren, enkelt tilgjengelig å bruke med Required<T>-typen.

type Required<T> = { [P in keyof T]-?: T[P] };

Det er imidlertid viktig å merke seg følgende når du bruker --strictNullChecks (som jeg gjør).

Merk at i --strictNullChecks-modus, når en homomorfisk avbildet type fjerner en ?-modifikator fra en egenskap i den underliggende typen, fjerner den også undefined fra typen til den egenskapen:

Jeg leter etter en måte å omgå denne bivirkningen på...

dvs. Jeg vil fjerne ?, men hvis |undefined finnes, vil jeg beholde det

Hvorfor?

Jeg har servergenererte grensesnitt som har valgfrie (?) egenskaper. Jeg ønsker å refaktorere serverkoden min og legge til/fjerne medlemmer. Derfor ønsker jeg en måte der jeg blir tvunget til å eksplisitt sette en verdi (eller eksplisitt sette til undefined) for hver egenskap, enten den er nødvendig eller ikke.

Dilemma :

  • Hvis jeg bruker Required<T> så svelger det min | undefined, men bare hvis det også er et ? til stede.

Så det er ganske ironisk at følgende:

export type RequiredDog = Required <{

    bark: 'loud' | 'quiet' | undefined,
    bite?: 'nip' | 'clamp' | undefined
}>; 

blir faktisk dette:

type RequiredDog = {
    bark: "loud" | "quiet" | undefined;
    bite: "nip" | "clamp";
}

Så bite, som var MER valgfritt enn bark, er nå faktisk MINDRE valgfritt!

Er det en måte å fjerne ? uten å også fjerne undefined ?

Svar #1

Hvis jeg forsøkte å implementere en Required<> som ikke fjerner undefined fra egenskaper, ville jeg sannsynligvis gjort det slik:

type RequiredKeepUndefined<T> = { [K in keyof T]-?: [T[K]] } extends infer U
  ? U extends Record<keyof U, [any]> ? { [K in keyof U]: U[K][0] } : never
  : never;

Det jeg gjør her med en bunke av kartlagte og betingede typer, er å pakke verditypene inn i en en-tuple, gjøre dem Required, og deretter pakke ut en-tuplene. Dette er liknende i ånd til svaret ditt, men det støter ikke på problemer som oppstår hvis noen bruker "undefined" som strengliteral i egenskaper.

Du kan se det oppføre seg som ønsket:

interface Dog {
    bark: "loud" | "quiet" | undefined;
    bite?: "nip" | "clamp" | undefined;
}

type RequiredKeepUndefinedDog = RequiredKeepUndefined<Dog>
/* type RequiredKeepUndefinedDog = {
    bark: "loud" | "quiet" | undefined;
    bite: "nip" | "clamp" | undefined;
} */

Du kan tenke på det som følgende transformasjoner

  • {bark: "loud" | "quiet" | undefined, bite?: "nip" | "clamp" | undefined}
  • {bark: ["loud" | "quiet" | undefined], bite?: ["nip" | "clamp" | undefined] | undefined}
  • {bark: ["loud" | "quiet" | undefined], bite: ["nip" | "clamp" | undefined]}
  • {bark: "loud" | "quiet" | undefined, bite: "nip" | "clamp" | undefined}

Ok, håper det hjelper; lykke til!

Lenke til kode

Svar #2

Her er hva vi bruker:

type RequiredWithUnknown<T> = {
  [K in keyof Required<T>]: T[K]
}

TS-lekeplass

Det bør være lett å forstå. Men kort sagt:
Typen får alle nøkler K av T. På grunn av Required<T>, får den virkelig alle nøklene, selv om de er valgfrie. Verdiene tas deretter fra T med disse obligatoriske nøklene.

Det fungerer bare for første nivå. Nestede objekter forblir uendret.

Relaterte artikler