Typescript'te değişken uzunlukta ve regex ile eşleşen bir string türünü nasıl tanımlarsınız?
Onaltılık bir dizeyi temsil eden bir tür nasıl oluşturulur?
let str: ByteString = "f1afe3"; // valid
let str1: ByteString = "fa1" // invalid, hex string length should be even
let str2: ByteString = "hello" //invalid, only hex allow
let str3: ByteString = "ffeeaa3300"; // valid
Burada ve burada örnekler buldum, ama ikisi de yalnızca sabit uzunlukta dizelere izin veriyor. Bunları rasgele uzunluktaki dizeler için genişletmek mümkün mü?
Cevap #1
TypeScript'te bu şekilde çalışan özel bir tür yoktur. microsoft/TypeScript#6579 adresinde düzenli ifade ile doğrulanan string türleri için uzun süredir bekleyen bir özellik talebi vardı, burada muhtemelen şöyle bir şey yazabileceğiniz belirtilmişti
// INVALID TYPESCRIPT, DO NOT TRY THIS:
type ByteString = r/^(?:[0-9a-fA-F][0-9a-fA-F])*$/
ve bu sadece çalışırdı. Ne yazık ki TypeScript'te böyle türler yok. Bunu tür sistemi içinde normal bir ifade ile yapamazsınız.
Yukarıdaki sorun, şablon literal tiplerinin tanıtılmasından sonra kapatıldı çünkü bunlar string literal tiplerinin manipülasyonuna izin veriyor. Kullanım durumunuz için şablon literal tiplerini kullanmayı deneyebiliriz, ancak bizi durduran birkaç büyük sorun var.
İlk olarak, geçerli bir onaltılık basamak için bir birleşim türü oluşturabilirsiniz:
type _HexDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' |
'A' | 'B' | 'C' | 'D' | 'E' | 'F'
type HexDigit = _HexDigit | Lowercase<_HexDigit>;
Ve ardından geçerli çift rakamlar için bir tür yazın:
type Nybble = `${HexDigit}${HexDigit}`;
/* type Nybble = "00" | "01" | "02" | "03" | "04" | "05" | "06" | "07" | "08" | "09" |
"0A" | "0B" | "0C" | "0D" | "0E" | "0F" | "0a" | "0b" | "0c" | "0d" | "0e" | "0f" |
"10" | "11" | "12" | "13" | "14" | "15" | "16" | "17" | "18" | "19" |
"1A" | "1B" | "1C" | "1D" | "1E" | "1F" | "1a" | "1b" | "1c" | "1d" | "1e" | "1f" |
"20" | "21" | "22" | "23" | "24" | "25" | "26" | "27" | "28" | "29" |
"2A" | "2B" | "2C" | "2D" | "2E" | "2F" | ... */
Ama sonra daha ileri gitmeye çalışırken takılıp kalıyorsunuz. Dil, döngüsel şablon literal tipleri yazmanıza izin vermiyor, bu yüzden bunu yapamazsınız:
type ByteStringX = "" | `${Nybble}${ByteStringX}`; // error!
// ~~~~~~~~~~~ <--
// Type alias 'ByteStringX' circularly references itself.
O döngüyü açmayı deneyebilirsin, ama denediğin anda derleyici, ortaya çıkan birliğin çok büyük olduğunu şikayet ediyor:
type ByteString0 = "" | Nybble | `${Nybble}${Nybble}`; // error!
// ----------------------------> ~~~~~~~~~~~~~~~~~~~~
// Expression produces a union type that is too complex to represent.
TypeScript'teki birlikler yalnızca 100.000 üyeye kadar bir şey tutabilir... ve geçerli dört haneli 230.000'den fazla onaltılık string var. Yani takılıp kaldık. ByteString'i belirli bir tür olarak yazmanın bir yolu yok.
Yapabileceğimiz şey, T türü bir aday string literal tipini doğrulayan bir genel tip ByteString<T> yazmaktır, böylece T yalnızca ve yalnızca geçerli bir bayt dizisi tipiyse T extends ByteString<T> olur. Yani bir tip yerine bir kısıtlama gibi davranır. Bunu kullanabilmek için, genel tip argümanı T'yi çıkarsamak için bir yardımcı işlev gerekir. Yani, bunun yerine
type ByteString = ...
const x: ByteString = "..."
sahip olurdun
type ByteString<T extends string> = ...
function byteString<T extends string> = ...
const x = byteString("...");
İşte bunun nasıl çalışabileceği:
type ByteString<T extends string, A extends string = ""> =
T extends "" ? A :
T extends `${infer D0 extends HexDigit}${infer D1 extends HexDigit}${infer R}`
? ByteString<R, `${A}${D0}${D1}`> : `${A}${Nybble}`
const byteString = <T extends string>(
str: T extends ByteString<T> ? T : ByteString<T>
) => str;
Yani ByteString<T> kuyruk-öz yinelemeli koşullu bir tiptir. Eğer T boş string ise bunu kabul ederiz; işte temel durum budur. Aksi takdirde T'yi ilk iki onaltılık basamak olan D0 ve D1 ile geri kalan string R olarak ayırmayı deneriz. Eğer bu ayrıştırma başarılı olursa, ByteString<R> hesaplayarak yinelemeye devam ederiz. Eğer bu ayrıştırma başarısız olursa, o zaman bir "kapanış" geçerli string döneriz, böylece hatalar umarım yardımcı bir hata mesajı üretir.
Hadi bunu test edelim:
let str = byteString("f1afe3"); // okay
let str1 = byteString("fa1"); // error!
// Argument of type '"fa1"' is not assignable to parameter of type
// '"fa00" | "fa01" | "fa02" | "fa03" | "fa04" | "fa05" | "fa06" | ...
let str2 = byteString("hello"); // error!
// Argument of type '"hello"' is not assignable to parameter of type '"00" | "01" |
// "02" | "03" | "04" | "05" | "06" | "07" | "08" | "09" | ...
let str3 = byteString("ffeeaa3300"); // okay
Bu, istediğiniz şekilde davranır. str0 ve str3 kabul edilirken, str1 ve str2 girişin uygunsuz olduğuna dair hata mesajları üretir. "fa1" başarısız olur ve `fa$ {Nybble}` ile karşılaştırılır, çünkü "1" geçerli bir çift rakam değildir. Ve "hello" başarısız olur ve `$ {Nybble}` ile karşılaştırılır, çünkü "he" geçerli bir çift rakam değildir.
Yani bu, istediğinize ulaşabileceğimiz en yakın şey. Eğer kullanım durumunuz için uygunsa, bu harika.
Eğer değilse, o zaman microsoft/TypeScript#41160 adresinde düzenli ifade ile doğrulanan dize türleri için hâlâ açık bir sorun var. O soruna gidip ona bir 👍 verebilir ve kullanım durumunuzu ve neden bir özyinelemeli şablon literal kısıtlamasının yeterli olmadığını açıklayabilirsiniz.
Koda yönelik oyun alanı bağlantısı
