typescript - Jenerikler üzerinde InstanceType kullanmak neden yanlış
Generiklerde InstanceType kullanmak neden yanlış? Kovaryant mı yoksa kontravaryant mı?
interface Ctor {
new(): Instance;
}
interface Instance {
print(): void;
}
function f1<T extends Ctor>(ctor: T) {
// Error: Type 'Instance' is not assignable to Type 'InstanceType<T>'
const ins: InstanceType<T> = new ctor();
ins.print();
}
function f2(ctor: Ctor) {
// No error
const ins: InstanceType<Ctor> = new ctor();
ins.print();
}
Oyun Alanı Bağlantısı
Cevap #1
Şu düşüncem var: Typescript, new ctor() ifadesinin derleme zamanı türünü, ctor türüne (ki bu T) bakarak belirler, T’nin bir yapıcı imzasına sahip olması gerektiğini kontrol eder (sahiptir) ve ardından T’nin yapıcı imzasının ne döndürdüğünü bulur. Ama T, Ctor’un herhangi bir alt türü olabileceğinden, yapıcı imzası Instance’ın herhangi bir alt türünü döndürebilir.
Bir derleyicinin bu durumda yapabileceği iki makul şey vardır:
- Ya yeni bir tür değişkeni icat edin (buna
R extends Instancediyelim) venew ctor()ifadesinin türününRolmasına izin verin, - ya da ifadenin türünün
Instanceolmasına izin ver.
Typescript bunu sonuncusunu yapar, belki de Typescript yalnızca siz ona söylemediğiniz sürece tip değişkenleri oluşturmaz; asla bunu örtük olarak yapmaz. Bu, ifadenin InstanceType<T> atamasının tür kontrolü yapılması için gereken tipten daha zayıf bir tipe sahip olmasına neden olur, çünkü InstanceType<T> gerçekten bir tür değişkeni R oluşturur (koşullu bir tipteki infer cümleciği kullanarak), bu da Instance'ın rastgele bir alt türü olabilir.
Yani new ctor() ifadesinin türü Instance'dır, ancak atama hedefinin tür notasyonu (gerçekte olmasa da, bir anlamda) herhangi bir Instance alt türü olabilecek bir tür değişkeni R'dir, bu nedenle bir tür hatası vardır.
Olası bir çözüm, dönüş türü için açıkça bir tür değişkeni oluşturmaktır, şöyle:
function f1<R extends Instance, T extends {new(): R}>(ctor: T) {
const ins: R = new ctor();
ins.print();
}
Bununla birlikte, bu yalnızca işlevinizin başka bir şekilde R ile ilgilenmesi durumunda yapılmaya değer, örneğin dönüş türü R içeren bir işlevse. Aksi takdirde, ins kullanan kodunuz, Instance dahil olmak üzere R'nin herhangi bir gerçekleştiriminde çalışmak zorunda olacağından, türünü zaten Instance olarak declare etmeniz daha mantıklı olur. (Benzer şekilde, işleviniz başka bir yerde T kullanmıyorsa, parametre türünü Ctor olarak belirlemeniz daha iyi olur).
Cevap #2
Çünkü T'nin Ctor'u genişlettiğini söylediniz, bu da onun tam olarak bir Ctor olmadığı anlamına gelir. Yeni bir şey döndüren Instance|Instance2 olabilir. Typescript'in bilmesi için bir yolu yok. Ve Ctor, her zaman yapıcısından bir Instance döndürdüğünü bildirdiği için, doğru türü doğru şekilde türetmeyi bilmiyor.
Bunu generikler ve türetilmiş türlerle çalıştırmak istiyorsanız, türetilmiş türün bir Generic parametreyle çıkarılmasına izin vermeniz gerekir.
interface Ctor<TInstance extends Instance> {
new(): TInstance;
}
interface Instance {
print(): void;
}
function f1<TInstance extends Instance>(ctor: Ctor<TInstance>) {
const ins: TInstance = new ctor();
ins.print();
}
