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)

Relaterte artikler