javascript - Arayüzlerde Typescript statik metotlar
Kendi başıma uygulamak zorunda kalmadan bir sınıfın bazı statik metodlara sahip olmasını zorunlu kılacak bir yol arıyorum (bir arayüzün normal metodları tanımlayabilmesi gibi). Arayüzler statik metodları desteklemediği için, aşağıdaki kod çalışmıyor.
interface MyInterface {
static fromJSON(json: string): MyInterface
toJSON(): object
}
Soyut sınıflar istediğim şey değil, çünkü bunlar geliştiricinin metodu kendisinin yazmasını gerektirmiyor, ama benim bunu uygulamam gerekiyor.
Bunu gerektirmeden, çok fazla özel mantık yazmadan benzer bir şey var mı?
Yukarıdaki arayüzü kullanarak, aşağıdaki uygulama kabul edilmemelidir:
class MyClass implements MyInterface {
// Missing static method "fromJSON"
toJSON() {
return {}
}
}
Bu da olmamalı:
class MyClass implements MyInterface {
static fromJSON(json: string) {
return 123 // Wrong type
}
toJSON() {
return {}
}
}
Ama bu kabul edilmelidir:
class MyClass implements MyInterface {
static fromJSON(json: string) {
return new MyClass()
}
toJSON() {
return {}
}
}
Cevap #1
TypeScript'te bir sınıfın statik tarafını kısıtlamak için gerçekten fazla destek yok. Bu eksik bir özellik; genel özellik talebi için microsoft/TypeScript#14600'a, sadece sınıflarda static implements desteği için microsoft/TypeScript#33892'ye ve sadece abstract static sınıf üyeleri desteği için microsoft/TypeScript#34516'ya bakın.
Sizin gösterdiğiniz biçimde bir interface üyelerinin static olması gibi bir şeyin önündeki en büyük engel, tür sisteminin bunu sizin istediğiniz şekilde çalışacak biçimde anlamasının zor olmasıdır. Uzun süredir açık olan bir sorun var, microsoft/TypeScript#3841, sınıfın constructor özelliğinin güçlü bir şekilde tiplenmesini talep ediyor. Şu anda bu özellik yalnızca Function tipi ile tanımlı:
class Foo {
instanceProp: string = "i"
static staticProp: string = "s"
}
const foo = new Foo();
foo.constructor.staticProp; // error!
// -----------> ~~~~~~~~~~
// Property 'staticProp' does not exist on type 'Function'
Bunun kolayca yapılamamasının bazı karmaşık nedenleri var, sorun içinde açıklanmış, ama esasen problem şudur: alt sınıf yapıcılarının üst sınıf yapıcılarının gerçek alt türleri olmaları şart değil:
class Bar extends Foo {
subInstanceProp: string;
constructor(subInstanceProp: string) {
super();
this.subInstanceProp = subInstanceProp;
}
}
const bar = new Bar("hello");
Burada, Bar yapıcısı tür olarak new (subInstanceProp: string) => Bar olup, Foo yapıcısının türüne, yani new() => Foo türüne atanamaz. extends ile, bar Foo'ya atanabilir olmalıdır. Ancak eğer bar.constructor, Foo['constructor'] türüne atanamazsa, her şey bozulur.
Bunun etrafından dolaşmanın yolları olabilir, ama şu ana kadar hiçbir şey uygulanmadı.
Tüm bunlar, MyInterface türünde bir nesneye bakmanın ve onu oluşturanın bir fromJSON metoduna sahip olduğundan emin olmanın hiçbir yolu olmadığı anlamına gelir. Bu nedenle interface tanımlarının içinde static bulundurmak gerçekten faydalı bir şekilde davranmaz.
microsoft/TypeScript#33892 ve microsoft/TypeScript#34516'taki taleplerin bu sorunu yok. Eğer bunu yazabilseydiniz:
class MyClass implements MyInterface static implements MyInterfaceConstructor {
// not valid TS, sorry ------------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
toJSON() { return "" };
static fromJSON(json: string) { return new MyClass() };
}
veya bu:
abstract class MyAbstractClass {
abstract toJSON(): string;
abstract static fromJSON(json: string): MyAbstractClass
// ------> ~~~~~~
// not valid TS, sorry
}
Bunu yapmanın bir yolu olurdu. Ne yazık ki, TS4.1 itibarıyla bu özelliklerin hiçbiri uygulanmamış, bu yüzden ilerlemenin tek yolu geçici çözümlerle devam etmektir.
Yukarıda yazdığım MyInterface ve MyInterfaceConstructor arayüzlerini alalım ve bunlarla neler yapabileceğimizi görelim. Şu anda yalnızca implements MyInterface ile örnek tarafını kısıtlayabiliriz:
class MyClass implements MyInterface {
toJSON() { return "" };
static fromJSON(json: string) { return new MyClass() };
}
Biz static implements MyInterfaceConstructor yazamayız. Ama staticImplements adında hiçbir şey yapmayan bir yardımcı fonksiyon oluşturabilir ve onu çağırabiliriz:
function staticImplements<T>(ctor: T) { }
staticImplements<MyInterfaceConstructor>(MyClass); // okay
Bunun herhangi bir hata vermeden derleniyor olması, MyClass'in statik tarafının kabul edilebilir olduğunun garantisidir. Çalışma zamanında bu bir işlem yapmaz, ancak derleme zamanında bu değerli bir bilgidir. Yanlış yaptığımızda ne olduğunu görelim:
class MyClassBad implements MyInterface {
toJSON() {
return ""
}
}
staticImplements<MyInterfaceConstructor>(MyClassBad); // error!
// ------------------------------------> ~~~~~~~~~~
// Property 'fromJSON' is missing in type 'typeof MyClassBad'
// but required in type 'MyInterfaceConstructor'.
class MyClassAlsoBad implements MyInterface {
static fromJSON(json: string) {
return 123 // Wrong type
}
toJSON() {
return ""
}
}
staticImplements<MyInterfaceConstructor>(MyClassAlsoBad); // error!
// ------------------------------------> ~~~~~~~~~~~~~~
// The types returned by 'fromJSON(...)' are incompatible between these types.
function validMyClass(ctor: MyInterfaceConstructor) { }
Bunlar aradığınız hatalardır. Evet, statik kısıtlama ve hatalar kodda tam olarak istediğiniz yerde değil, ama en azından bunu ifade edebilirsiniz. Bu bir geçici çözümdür.
Bu geçici çözümün başka versiyonları da var, muhtemelen dekoratörler kullanılarak (bu dekoratörler bir tür eskimiş veya JS'de dekoratör desteği tamamlanana kadar beklemede), ama temel fikir şudur: Yapıcı türünü arayüzünüzün "statik kısmına" atamayı deneyin ve bir şeylerin başarısız olup olmadığını görün.
Koda yönelik oyun alanı bağlantısı
