typescript - Hvordan tvinge et Set-objekt til å inneholde alle verdier i en enum - TS
Jeg har følgende enum:
enum GREETINGS { HELLO='Hello', HI='Hi' }
Jeg vil lage en type for en Set-datastruktur som vil tvinge brukeren til å inkludere alle enum-verdiene i den, som vist nedenfor:
type MyGreetings=Set<GREETINGS>
Det forventede resultatet bør få følgende linje til å bli uthevet med en advarsel som sier at ikke alle nødvendige verdier er inkludert i resultatet:
const result: MyGreetings=new Set([GREETINGS.HELLO]);
Svar #1
Et problem er at TypeScript anser Set<GREETINGS.HELLO> for å være tildelbar til/kompatibel med Set<GREETINGS>; i teorien burde det bety at hvis du ønsker at Set<GREETINGS> skal være tildelbar til MyGreetings, så vil ved transitivitet av tildelbarhet også Set<GREETINGS.HELLO> være tildelbar til MyGreetings, og du stoppes før du har begynt. Men:
Faktumet at Set<GREETINGS.HELLO> kan tildeles til Set<GREETINGS> bruker faktisk et bevisst hull i typesystemet: metodeparameter-bivarians. Metodeparameter-bivarians er veldig nyttig, men det er ikke typesikkert:
const justHello = new Set<GREETINGS.HELLO>([GREETINGS.HELLO]);
const stillJustHello: Set<GREETINGS> = justHello;
stillJustHello.add(GREETINGS.HI) // <-- no error, oops
Ved å «utvide» justHello til stillJustHello, har kompilatoren glemt at den ikke skal akseptere noen medlem bortsett fra GREETINGS.HELLO.
Så en måte å komme nærmere det du vil på er å bruke --strictFunctionType kompilatoralternativet (som er en del av --strict-pakken med kompilatoralternativer og generelt anbefales) og omskrive minst én Set-metodesignatur ved å bruke en funksjon-egenskapstype i stedet for en metodesignatur. La oss se på add() og se forskjellen:
type Method = { add(x: GREETINGS): void } // method syntax
const m: Method = new Set<GREETINGS.HELLO>(); // okay
type FuncProp = { add: (x: GREETINGS) => void } // function syntax
const f: FuncProp = new Set<GREETINGS.HELLO>(); // error
Så la oss prøve noe sånt som:
type MyGreetings = Set<GREETINGS> & { add: (g: GREETINGS) => MyGreetings };
Hvis vi bruker det, får du ønsket resultat:
const result: MyGreetings = new Set([GREETINGS.HELLO]); // error!
// Type 'GREETINGS' is not assignable to type 'GREETINGS.HELLO'
const okay: MyGreetings = new Set([GREETINGS.HELLO, GREETINGS.HI]); // okay
Hurra!
Vel, på en måte. Kompilatoren kan egentlig ikke vite nøyaktig hvilke verdier som har blitt sendt inn i et Set. Den kan slutte at new Set(...) er av typen Set<X> for noen X, men det finnes måter å manuelt spesifisere en bredere X eller få kompilatoren til å slutte seg til en bredere X. Og da er all fremgang fra ovenfor tapt.
For eksempel, siden et Set<GREETINGS> kan inneholde bare et delsett av GREETINGS, kan noen gjøre følgende manuelle spesifikasjon:
const oops: MyGreetings = new Set<GREETINGS>([GREETINGS.HELLO]) // no error
Eller noen kunne få kompilatoren til å utlede en union på denne måten:
const alsoOops: MyGreetings = new Set([
Math.random() < 0.5 ? GREETINGS.HELLO : GREETINGS.HI
]); // no error
Ingen av disse kan være en feil; verdien på høyre side av likhetstegnet er en Set<GREETINGS>. Og du trenger at Set<GREETINGS> kan tildeles til MyGreetings, så tildelingene er heller ikke feil. Det er ikke mye som kan gjøres med dette.
Hvis du i praksis ikke tror at noen vil lage et sett på disse eller andre patologiske måter, kan du kanskje bruke MyGreetings. Ellers, hvis du virkelig trenger å garantere noe, bør du vurdere å endre designet ditt på en eller annen måte. Men dette er sannsynligvis utenfor spørsmålets omfang, så jeg stopper der.
Lenke til lekeplass for kode (testet og fungerer for TypeScript-versjon 3.5.1 og høyere)
