TypeScript sınıf alanını indeks imzası kullanarak nasıl tanımlanır
type TField = {
field1: string;
field2: boolean;
field3: TMyCustom;
}
class Test1 {
// I want to declare class field with this index signature
[key: keyof TField]: TField[typeof key]
// I don't want write field one by one
// field1: string;
// field2: boolean;
// field3: TMyCustom;
constructor() {
// But TS Can't infer from index signature
// Error : Property 'field1' does not exist on type 'Test1'
this.field1 = 'initialField1'
// Error : Property 'field2' does not exist on type 'Test1'
this.field2 = true;
// Error : Property 'field3' does not exist on type 'Test1'
this.field3 = '';
}
}
Typescript'in indeks imzasını kullanarak sınıf alanı ilan etmek istiyorum.
Yukarıdaki örnekte, durumu basitleştirmek için TField'ın yanına Test1 sınıfını tanımlıyorum.
Ama proje kodumda, TField'ı birçok dosyadan içe aktarmam gerekiyor. Bu yüzden Test1'in alanını tek tek ilan edemem.
TS index imzasını yazmakta yanlış mı yaptığım yoksa TS’nin bu tür bir tipi destekleyip desteklemediğinden emin değilim.
----------- soru eklendi
type TField = {
field1: string;
field2: boolean;
field3: TMyCustom;
}
class Test0 {
// Previously, I declare "fields" property type of TField
fields: TField
// But now, I want to declare and access field1,2,3 at the class property level,
// not inside of the "fields" property.
constructor() {
this.fields = {
field1: 'initial field1',
field2: true,
field3: {some: 'object'} as TMyCustom,
}
}
}
İndeks imzasına dönüştürmeyi denemeden önce, kodum Test0 sınıfı gibi görünüyor.
Sizin "neden sadece field1,2,3'ü sınıf özelliği olarak değil de "fields" özelliğini bildirip field1,2,3'ü onun içine yerleştiriyorsunuz?" konusunda bir merakınız olduğunu biliyorum. Ama bu proje yapısıyla ilgili. O yüzden lütfen bir kenara bırakın.
field1,2,3 alanları fields özelliğinde bulunduğu için, field1,2,3 alanını şöyle değerlendirmeliyim test0Instance.fields.field1
Bu yüzden bu soruyu yazdım, bir sınıf alanını belirtmek için indeks imzasını kullanıp kullanamayacağımı.
Cevap #1
Ne yazık ki, indesk imzaları istediğiniz şeyi yapmaz. Bir indeks imzası, bir class veya interface'in rastgele string anahtarlarla sözlük gibi veya rastgele number (benzeri) anahtarlarla bir dizi gibi davranmasını bildirmenize izin verebilir ve TypeScript 4.4'ten itibaren, rastgele symbol anahtarlar veya `hello-$ {string}` gibi "desen" şablon stringlerini bile kullanabilirsiniz. Ama keyof TField gibi sınırlı bir dizi string literal anahtar veremezsiniz. Bunu yapabilseydiniz bile, derleyici her bir anahtar üzerinde ayrı ayrı işlem yapmanıza izin vermezdi; tüm anahtarlar aynı değer türüne sahip olurdu. Bu sizin istediğiniz şey değil.
Görünüşe göre söylemek istediğiniz şey, class'ın, [K in keyof TField]: TField[K] biçiminde bir eşlenmiş türe sahip anahtarları olduğu. Ancak eşlenmiş türlerin interface veya classlerin bir parçası olması izin verilmez. Onlar bağımsız türlerdir.
İstediğiniz şey için yerleşik bir çözüm yok. Alternatif bir evrende, TypeScript bunu yapmanıza izin verebilirdi:
class AlternateUniverseTest implements TField {
constructor(tfield: TField) {
this.field1 = tfield.field1;
this.field2 = tfield.field2;
this.field3 = tfield.field3;
}
}
veya implements TField ya da this.field1=... atamaları, derleyicinin AlternateUniverseTest'in TField ile aynı alanlara sahip olduğunu bilmesini sağlar. Ancak ne yazık ki implements TField, derleyicinin hangi özelliklerin mevcut olduğunu veya türlerini nasıl çıkardığını değiştirmez (bkz. microsoft/TypeScript#32082 ve birçok bağlantılı sorun); ve derleyici her durumda özellik bildiriminin atlanmasına izin vermez (bkz. microsoft/TypeScript#766).
Bu, demek oluyor ki, bu evrende, bu TypeScript sürümü ile, sınıf gövdenizin içinde bireysel özellikleri tek tek bildirmeniz gerekiyor. Ya da bunun etrafından dolaşmanız gerekiyor.
Bir çözüm yolu, genel bir sınıf fabrikası yardımcı işlevi yapmaktır. Ona bir nesne türü T belirtirsiniz ve o, { new (init: T): T} şeklinde bir sınıf yapıcısı döndürür (bu bir yapıcı imzasıdır):
function Wrapper<T extends object>(): new (init: T) => T {
return class {
constructor(init: T) {
Object.assign(this, init);
}
} as any;
}
Uygulama içindeki class ifadesinin hiçbir özelliği olmadığı görüldüğüne dikkat edin. Çalışma zamanında init içinde hangi özellikler varsa sınıfın onları alacağını biliyoruz, çünkü bu, Object.assign() ile this ilk parametre olarak verildiğinde yaptığı şeydir. Ama derleyici bunu göremez. Bu bizim için sorun değil, derleyiciye endişelenmemesini söylemek için as any tür bildirimi kullanabiliriz. Biz esas olarak Wrapper() çağrıldığında neler olduğunu önemsiyoruz:
class Test extends Wrapper<TField>() {
otherProp: number;
constructor(tfield: TField) {
super(tfield)
this.field1 // <-- this exists now, no error
this.otherProp = 100;
}
}
Test'i Wrapper<TField>() alt sınıfı yaparak, gövdesi zaten field1, field2 ve field3 hakkında ve türlerinin ne olduğu hakkında bilgi sahibi olan bir sınıfa sahip oluyoruz. super'i çağırıp ona geçerli bir TField iletmemiz gerekiyor; bu durumda bunu construct çağrısından alıyoruz (ama isterseniz sabit olarak da verebilirsiniz). Diğer özellikleri de normal şekilde ekleyebilirsiniz.
Bunun işe yaradığından emin olalım:
const test = new Test({
field1: "hello",
field2: true,
field3: ""
})
console.log(test.field1.toUpperCase()) // HELLO
console.log(test.otherProp.toFixed(2)) // 100.00
Güzel görünüyor. Derleyici mutlu ve umarım siz de öylesinizdir. Bu bir geçici çözüm, bu yüzden muhtemelen bazı sınırlamalar vardır. Örneğin, Test'in hiçbir şeyin alt sınıfı olmasını istemiyorsanız (veya başka bir şeyin alt sınıfı olması gerekiyorsa) bu yaklaşımı unutmanız veya en azından değiştirme gerekir.
Koda yönelik oyun alanı bağlantısı
