TypeScript'te enum anahtarlarını birleşik string olarak almak için genel tip?
Aşağıdaki typescript enumunu göz önünde bulundurun:
enum MyEnum { A, B, C };
Eğer o enum'un anahtarlarının birleşik string'lerinden oluşan başka bir tür istiyorsam, şunu yapabilirim:
type MyEnumKeysAsStrings = keyof typeof MyEnum; // "A" | "B" | "C"
Bu çok faydalı.
Şimdi bu şekilde genel olarak enum'lar üzerinde çalışan bir genel tür oluşturmak istiyorum, böylece bunun yerine şunu söyleyebilirim:
type MyEnumKeysAsStrings = AnyEnumKeysAsStrings<MyEnum>;
Bunun için doğru sözdizimini şöyle hayal ediyorum:
type AnyEnumKeysAsStrings<TEnum> = keyof typeof TEnum; // TS Error: 'TEnum' only refers to a type, but is being used as a value here.
Ama bu bir derleme hatası oluşturuyor: "'TEnum' yalnızca bir tipe referans verir, ancak burada bir değer olarak kullanılıyor."
Bu beklenmedik ve üzücü. Bunu kısmen şu şekilde çözebilirim: generiğin bildiriminin sağ tarafındaki typeof'u kaldırıp, belirli tipin bildiriminde tür parametresine ekleyerek:
type AnyEnumAsUntypedKeys<TEnum> = keyof TEnum;
type MyEnumKeysAsStrings = AnyEnumAsUntypedKeys<typeof MyEnum>; // works, but not kind to consumer. Ick.
Ancak bu geçici çözümü sevmiyorum, çünkü bu, tüketicinin jenerik üzerinde typeof belirtmeyi hatırlaması gerektiği anlamına geliyor ve bu hoş değil.
Başlangıçta istediğim gibi genel türü belirtmeme izin verecek herhangi bir sözdizimi var mı, tüketiciye karşı nazik olmak için?
Cevap #1
Hayır, tüketicinin A, B ve C anahtarlarına sahip olan nesneyi belirtmek için typeof MyEnum kullanması gerekecek.
UZUN AÇIKLAMA ÖNDE, BAZILARINI MUHTEMELEN ZATEN BİLİYORSUNUZ
Muhtemelen farkında olduğunuz gibi, TypeScript JavaScript'e statik bir tür sistemi ekler ve bu tür sistemi kod derlendiğinde silinir. TypeScript'in sözdizimi öyle ki bazı ifadeler ve deyimler çalışma zamanında var olan değerlere, diğer ifadeler ve deyimler ise yalnızca tasarım/derleme zamanında var olan türlere atıfta bulunur. Değerlerin türleri vardır, ancak kendileri tür değildir. Önemli olarak, kodda derleyicinin bir değer bekleyeceği ve bulduğu ifadeyi mümkün olduğunda değer olarak yorumlayacağı bazı yerler vardır; diğer yerlerde ise derleyici bir tür bekler ve bulduğu ifadeyi mümkün olduğunda tür olarak yorumlar.
Derleyici, bir ifadenin hem değer hem de tür olarak yorumlanmasının mümkün olup olmadığıyla ilgilenmez veya kafası karışmaz. Örneğin, aşağıdaki kodda null'un iki çeşidiyle tamamen mutlu olur:
let maybeString: string | null = null;
null öğesinin ilk örneği bir türdür ve ikincisi bir değerdir. Ayrıca bununla ilgili bir sorunu yoktur
let Foo = {a: 0};
type Foo = {b: string};
ilk Foo adlandırılmış bir değer iken, ikinci Foo adlandırılmış bir tiptir. Değer Foo'nun türünün {a: number} olduğunu, tip Foo'nun ise {b: string} olduğunu belirtin. Onlar aynı değil.
Hatta typeof operatörü bile çift yaşam sürer. typeof x ifadesi her zaman x'in bir değer olmasını bekler, ancak bağlama bağlı olarak typeof x kendisi bir değer veya tür olabilir:
let bar = {a: 0};
let TypeofBar = typeof bar; // the value "object"
type TypeofBar = typeof bar; // the type {a: number}
Satır let TypeofBar=typeof bar; JavaScript'e geçecek ve çalışma zamanında JavaScript typeof operatörünü kullanarak bir dize üretecektir. Ancak type TypeofBar=typeof bar; silinir ve TypeScript'in bar isimli değere atadığı statik türü incelemek için TypeScript tür sorgulama operatörünü kullanır.
Şimdi, TypeScript'teki isim tanıtıcı çoğu dil yapısı ya isimlendirilmiş bir değer ya da isimlendirilmiş bir tip oluşturur. İşte bazı isimlendirilmiş değer tanıtımları:
const value1 = 1;
let value2 = 2;
var value3 = 3;
function value4() {}
Ve işte adlandırılmış türlerin bazı tanıtımları:
interface Type1 {}
type Type2 = string;
Ama hem isimlendirilmiş bir değer hem de isimlendirilmiş bir tip oluşturan birkaç bildirim vardır ve yukarıdaki Foo gibi, isimlendirilmiş değerin tipi, isimlendirilmiş tip değildir. Önemli olanlar class ve enum’dir:
class Class { public prop = 0; }
enum Enum { A, B }
Burada, type Class, bir instance Class türüdür, oysa value Class constructor nesnesidir. Ve typeof Class Class değildir:
const instance = new Class(); // value instance has type (Class)
// type (Class) is essentially the same as {prop: number};
const ctor = Class; // value ctor has type (typeof Class)
// type (typeof Class) is essentially the same as new() => Class;
Ve, type Enum, bir sayımın bir elemanının tipidir; her elemanın tiplerinin birleşimidir. Value Enum ise anahtarları A ve B olan ve özellikleri sayımın elemanları olan bir objedir. Ve typeof Enum, Enum değildir:
const element = Math.random() < 0.5 ? Enum.A : Enum.B;
// value element has type (Enum)
// type (Enum) is essentially the same as Enum.A | Enum.B
// which is a subtype of (0 | 1)
const enumObject = Enum;
// value enumObject has type (typeof Enum)
// type (typeof Enum) is essentially the same as {A: Enum.A; B: Enum.B}
// which is a subtype of {A:0, B:1}
Şimdi sorunuza geri dönüyorum. Şöyle çalışan bir tür operatörü icat etmek istiyorsunuz:
type KeysOfEnum = EnumKeysAsStrings<Enum>; // "A" | "B"
Type Enum'i nereye koyduğunuz ve object Enum'in anahtarlarını nasıl alacağınız. Ama yukarıda gördüğünüz gibi, type Enum nesne Enum ile aynı değil. Ve maalesef type, değer hakkında hiçbir şey bilmiyor. Bu, şöyle demeye benziyor:
type KeysOfEnum = EnumKeysAsString<0 | 1>; // "A" | "B"
Açıkça, bunu böyle yazarsanız, 0 | 1 türüne "A" | "B" türünü üretecek bir şey yapamayacağınızı görürdünüz. Bunu çalıştırmak için, eşlemeyi bilen bir tür geçmeniz gerekir. Ve bu tür typeof Enum türüdür...
type KeysOfEnum = EnumKeysAsStrings<typeof Enum>;
ki bu gibi
type KeysOfEnum = EnumKeysAsString<{A:0, B:1}>; // "A" | "B"
bu mümkün... eğer type EnumKeysAsString<T>=keyof T ise.
Yani tüketicinin typeof Enum belirtmesini sağlamak zorunda kalıyorsunuz. Çözümler var mı? Belki bir değeri yapan bir şey kullanabilirsiniz, örneğin bir fonksiyon?
function enumKeysAsString<TEnum>(theEnum: TEnum): keyof TEnum {
// eliminate numeric keys
const keys = Object.keys(theEnum).filter(x =>
(+x)+"" !== x) as (keyof TEnum)[];
// return some random key
return keys[Math.floor(Math.random()*keys.length)];
}
O zaman arayabilirsin
const someKey = enumKeysAsString(Enum);
ve someKey türü "A" | "B" olacak. Evet ama sonra bunu tür olarak kullanmak için sorgulaman gerekecek:
type KeysOfEnum = typeof someKey;
bu da sizi tekrar typeof kullanmaya zorlar ve çözümünüzden bile daha uzun bir anlatım olur, özellikle bunu yapamayacağınız için:
type KeysOfEnum = typeof enumKeysAsString(Enum); // error
Blegh. Özür dilerim.
Özetlemek gerekirse:
- Bu mümkün değil
- Türler ve değerler blah blah
- Hâlâ mümkün değil
- Üzgünüm
Cevap #2
Daha kısa, daha basit çözüm
Yeni genel türler oluşturmak gerekli değildir.
Eğer bir enum ilan ederseniz
enum Season { Spring, Summer, Autumn, Winter };
Bu türe ulaşmak için yalnızca keyof typeof anahtar kelimelerini kullanmanız gerekir
let seasonKey: keyof typeof Season;
O zaman değişken beklendiği gibi çalışır
seasonKey = "Autumn"; // is fine
// seasonKey = "AA" <= won't compile
Cevap #3
Aslında mümkündür.
enum MyEnum { A, B, C };
type ObjectWithValuesOfEnumAsKeys = { [key in MyEnum]: string };
const a: ObjectWithValuesOfEnumAsKeys = {
"0": "Hello",
"1": "world",
"2": "!",
};
const b: ObjectWithValuesOfEnumAsKeys = {
[MyEnum.A]: "Hello",
[MyEnum.B]: "world",
[MyEnum.C]: "!",
};
// Property '2' is missing in type '{ 0: string; 1: string; }' but required in type 'ObjectWithValuesOfEnumAsKeys'.
const c: ObjectWithValuesOfEnumAsKeys = { // Invalid! - Error here!
[MyEnum.A]: "Hello",
[MyEnum.B]: "world",
};
// Object literal may only specify known properties, and '6' does not exist in type 'ObjectWithValuesOfEnumAsKeys'.
const d: ObjectWithValuesOfEnumAsKeys = {
[MyEnum.A]: "Hello",
[MyEnum.B]: "world",
[MyEnum.C]: "!",
6: "!", // Invalid! - Error here!
};
Oyun Alanı Bağlantısı
DÜZENLEME: Kısıtlama kaldırıldı!
enum MyEnum { A, B, C };
type enumValues = keyof typeof MyEnum;
type ObjectWithKeysOfEnumAsKeys = { [key in enumValues]: string };
const a: ObjectWithKeysOfEnumAsKeys = {
A: "Hello",
B: "world",
C: "!",
};
// Property 'C' is missing in type '{ 0: string; 1: string; }' but required in type 'ObjectWithValuesOfEnumAsKeys'.
const c: ObjectWithKeysOfEnumAsKeys = { // Invalid! - Error here!
A: "Hello",
B: "world",
};
// Object literal may only specify known properties, and '6' does not exist in type 'ObjectWithValuesOfEnumAsKeys'.
const d: ObjectWithKeysOfEnumAsKeys = {
A: "Hello",
B: "world",
C: "!",
D: "!", // Invalid! - Error here!
};
Oyun Alanı Bağlantısı
- Bu
const enumile de çalışıyor!
