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.
