TypeScript'te bir dizide türüne göre değer bulma
Bir dizide öğeleri türüne göre bulmak için bir yöntem geliştirmeye çalışıyorum. Bu özel düzenlemeyi göz önünde bulundurun:
abstract class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class Horse extends Animal {}
const animals: Animal[] = [new Dog, new Cat, new Horse];
Ben bir fonksiyon yazmaya çalışıyorum, bu fonksiyon animals içinden türüne göre bir öğe bulabilmeli, şöyle bir şey: findAnimal<Dog>(animals). Düz İngilizce ile: "animals içindeki Dog örneği olan ilk öğeyi bul." İşte bunun kaba bir taslağı:
function find<A extends Animal>(items: Animal[]): A {
return items.find((a: Animal) => a instanceof A) as A;
}
Bu derlenmiyor ve nedenini anlıyorum: o genel argüman derlemeden sonra artık mevcut değil. Ancak, bunun gibi bir şeyin çalışmasını sağlamanın bir yolu var mı? Fonksiyonu iki parametre alacak şekilde değiştirmeye kesinlikle razıyım:
function find(items: Animal[], A: NOT_SURE_WHAT_GOES_HERE): WHAT_TO_RETURN {
// ...
}
Ancak, bu değişiklikle ikinci argümanı veya dönüş tipini düzgün bir şekilde nasıl belirtileceğimden emin değilim. Seçeneklerim nelerdir?
Cevap #1
Genel türü, sınıf argümanının türünde ve dönüş değerinde şu şekilde kullanabilirsiniz:
function find<A extends Animal>(animals: Animal[], cls: new() => A): A | undefined {
return animals.find((value): value is A => value instanceof cls);
}
Dikkat edin, type predicate'i find geri çağırmasının dönüş değeri olarak kullanarak artık bir tür dönüşümüne gerek kalmıyor. Bu çok daha güvenli - animals içinde bir cls örneğini bulamayabilirsiniz, mevcut uygulamanızda potansiyel olarak undefined as A döndürüyor olabilirsiniz.
Bunu çağırdığınızda genel tip ikinci argümandan çıkarılabilir:
find(animals, Dog);
Oyun alanı
Cevap #2
Tamamen yeni bir fonksiyon oluşturmak yerine, aynı zamanda bir tür koruyucu olan bir arama koşulu oluşturabilirsiniz. Bu şekilde, saf Array#find yöntemini çağırabilirsiniz, ancak doğru türü geri aldığınızdan da emin olursunuz:
abstract class Animal {}
class Dog extends Animal { bark() {} }
class Cat extends Animal { meow() {} }
class Horse extends Animal { neigh() {}}
const animals: Animal[] = [new Dog, new Cat, new Horse];
const animal = animals.find((item: Animal): item is Dog => item instanceof Dog);
if (animal) { //make sure it's not `undefined`
animal.meow(); //Error - it's not a Cat
animal.neigh(); //Error - it's not a Horse
animal.bark(); //OK - it's a Dog
}
Oyun Alanı Bağlantısı
Bu daha sonra genelleştirilebilir ve kolayca yeniden kullanılabilir bir curry fonksiyonu olarak yapılabilir:
abstract class Animal {}
class Dog extends Animal { bark() {} }
class Cat extends Animal { meow() {} }
class Horse extends Animal { neigh() {}}
const animals: Animal[] = [new Dog, new Cat, new Horse];
const search = <A extends Animal>(species: new() => A) => (item: Animal): item is A =>
item instanceof species;
const animal = animals.find(search(Dog));
if (animal) { //make sure it's not `undefined`
animal.meow(); //Error - it's not a Cat
animal.neigh(); //Error - it's not a Horse
animal.bark(); //OK - it's a Dog
}
Oyun Alanı Bağlantısı
Bu hâlâ bir yargı olduğundan, bir tane alan varsayılan dizi yöntemlerinin herhangi biri için bunu yeniden kullanabiliriz
const dogs = animals.filter(search(Dog)); //=> Dog[]
dogs.forEach(dog => dog.bark()); //OK
if (animals.every(search(Dog))) {
animals.forEach(dog => dog.bark()); //OK
}
Oyun Alanı Bağlantısı
Bu fonksiyon, herhangi bir üst/alt sınıf ilişkisinde ve herhangi bir değişken sayıda yapıcıya izin vermek için daha genel hale getirilebilir. Arayüzlerinizin boş olmaması gerektiğini unutmayın [https://github.com/microsoft/TypeScript/wiki/FAQ#why-are-all-types-assignable-to-empty-interfaces]:
abstract class Animal { isPet?: boolean }
class Dog extends Animal { bark() {} }
class Cat extends Animal { constructor(name: string) {super(); } meow() {} }
class Horse extends Animal { constructor(age: number, recehorse: boolean) {super();} neigh() {}}
declare const animals: Animal[];
const search = <Super, Child extends Super>(specific: new(...args: any[]) => Child) => (item: Super): item is Child =>
item instanceof specific;
const dog = animals.find(search(Dog)); //OK
const cat = animals.find(search(Cat)); //OK
const horse = animals.find(search(Horse)); //OK
const dogs = animals.filter(search(Dog)); //OK
if (animals.every(search(Dog))) { } //OK
animals.find(search(String)); // Error - String is not assignable to Animal
declare const cats: Cat[];
cats.find(search(Dog)); //Error - Dog is not a assignable to Cat
Oyun Alanı Bağlantısı
Ancak, genel tip çıkarımının yalnızca ikinci işlevin bir tipe sahip olduğu sürece çalıştığını unutmayın. Yukarıdaki örnek, genel argümanları açıkça sağlamayı gerektirmez çünkü birinci parametre olarak Dog çağrılır ve bu da Child’ı ayarlar, ardından ikinci işlev bir dizi geri çağırması olarak geçirilir, bu yüzden hemen Super için bir tip olarak Animal veya Cat alır. Ancak, bunu yapmak:
const searchDog = search(Dog);
Super jenerik argümanını ayarlamaz ve bu nedenle unknown olarak ayarlanır. Bir önermeyi türetmek ve türleri ayarlamak istiyorsanız, şunları yapmanız gerekir
const searchDog = search<Animal, Dog>(Dog);
