TypeScript type ignorere tilfelle
Jeg har denne typedefinisjonen i TypeScript:
export type xhrTypes = "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "CONNECT" | "HEAD";
Dessverre er dette store- og småbokstavfølsomt. Finnes det noen måte å definere det uten å være følsom for store og små bokstaver?
Svar #1
NYTT SVAR FOR TYPESCRIPT 4.1+
Velkommen tilbake! Nå som TypeScript 4.1 har introdusert template literal-typer og de innebygde strengkartleggingstypene Uppercase/Lowercase, kan vi nå svare på dette spørsmålet uten å måtte bruke regulære uttrykk-typer.
Det er to hovedtilnærminger. "Brute force"-tilnærmingen bruker i stor grad rekursive betingede typer og unions for å gjøre din xhrTypes om til en konkret union av alle mulige måter å skrive disse strengene på der store og små bokstaver ikke betyr noe:
type xhrTypes = "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "CONNECT" | "HEAD";
type AnyCase<T extends string> =
string extends T ? string :
T extends `${infer F1}${infer F2}${infer R}` ? (
`${Uppercase<F1> | Lowercase<F1>}${Uppercase<F2> | Lowercase<F2>}${AnyCase<R>}`
) :
T extends `${infer F}${infer R}` ? `${Uppercase<F> | Lowercase<F>}${AnyCase<R>}` :
""
type AnyCaseXhrTypes = AnyCase<xhrTypes>;
Hvis du inspiserer AnyCaseXhrTypes, vil du se at det er en union med 368 medlemmer:
/* type AnyCaseXhrTypes = "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" |
"CONNECT" | "HEAD" | "GEt" | "GeT" | "Get" | "gET" | "gEt" | "geT" | "get" |
"POSt" | "POsT" | "POst" | "PoST" | "PoSt" | "PosT" | "Post" |
... 346 more ... | "head" */
Du kan deretter bruke denne typen i stedet for xhrType hvor som helst du ønsker ufølsomhet for store og små bokstaver:
function acceptAnyCaseXhrType(xhrType: AnyCaseXhrTypes) { }
acceptAnyCaseXhrType("get"); // okay
acceptAnyCaseXhrType("DeLeTe"); // okay
acceptAnyCaseXhrType("poot"); // error! "poot" not assignable to big union
Problemet med brute force-tilnærmingen er at den ikke skalerer godt med flere eller lengre strenger. Unionstyper i TypeScript er begrenset til 100 000 medlemmer, og rekursive betingede typer går egentlig bare rundt 20 nivåer dypt maksimalt før kompilatoren klager. Så ethvert moderat langt ord eller moderat lang liste med ord vil gjøre tilnærmingen ovenfor urealistisk.
type xhrTypes = "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "CONNECT" | "HEAD"
| "LONG STRINGS MAKE THE COMPILER UNHAPPY";
type AnyCaseXhrTypes = AnyCase<xhrTypes>; // error!
// Type instantiation is excessively deep and possibly infinite.
// Union type is too complex to represent
En måte å håndtere dette på er å gå bort fra å bruke en spesifikk konkret union, og i stedet bytte til en generisk typerepresentasjon. Hvis T er typen til en strengverdi som sendes til acceptAnyCaseXhrType(), så er alt vi ønsker å gjøre å sørge for at Uppercase<T> kan tilordnes til xhrType. Dette er mer en begrensning enn en type (selv om vi ikke kan bruke generiske begrensninger direkte for å uttrykke dette):
function acceptAnyCaseXhrTypeGeneric<T extends string>(
xhrType: Uppercase<T> extends xhrTypes ? T : xhrTypes
) { }
acceptAnyCaseXhrTypeGeneric("get"); // okay
acceptAnyCaseXhrTypeGeneric("DeLeTe"); // okay
acceptAnyCaseXhrTypeGeneric("poot"); // error! "poot" not assignable to xhrTypes
Denne løsningen krever at du flytter generiske typeparametere rundt på steder du ellers kanskje ikke trenger dem, men den skalerer godt.
Så, der har du det! Alt vi måtte gjøre var å vente i... (ser på notater)... 3 år, og TypeScript leverte!
Lenke til kode på lekeplassen
Svar #2
Bare så det er et svar på dette innlegget: Nei, det er ikke mulig.
Oppdatering 15.05.2018: Fremdeles ikke mulig. Det nærmeste man kommer, strengtyper validert med regex, ble ikke godt mottatt sist gang det ble foreslått på språkutviklingsmøtet.
