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);