Typescript'te fonksiyon argümanlarına göre dönüş türlerini nasıl belirtirim
Aşağıdaki fonksiyonu, öğeleri bir özelliklerine göre gruplamak için kullanıyorum. Örneğin, arabalar dizisini yıl bazında gruplamak istiyorum.
Yani yılı anahtar olarak geçiriyorum, arabalar dizisi eşya olarak ve arabaları eşya olarak geçerim.
Ve fonksiyon, şu şekilde nesneler dizisi döndürür
[
{
year: 2012,
cars: [{make: ‘Audi’, model: ‘r8’}, {make: ‘Ford’, model: ‘mustang’}]
}
]
Benim sorum, döndürülen değerler için tipleri veya arayüzleri nasıl beyan edebilirim? Lütfen farklı diziler kullanacağımı, örneğin planlanmış tarihe göre gruplamak için bir restoranlar dizisi gibi, bu yüzden tiplerin dinamik olması gerektiğini unutmayın.
const cars = [
{
'make': 'audi',
'model': 'r8',
'year': '2012'
}, {
'make': 'audi',
'model': 'rs5',
'year': '2013'
}, {
'make': 'ford',
'model': 'mustang',
'year': '2012'
}, {
'make': 'ford',
'model': 'fusion',
'year': '2015'
}, {
'make': 'kia',
'model': 'optima',
'year': '2012'
},
];
// Group items by property
export function groupBy(key, items, itemsName) {
// Crate groups with provided key
const groupsObj = items.reduce((acc, curr) => {
// Property to create group with
const property = curr[key];
// If property exists in acc then,
// add the current item to the property array
if (property in acc) {
return { ...acc, [property]: [...acc[property], curr] };
}
// Else create a property and
// add the current item to an array
return { ...acc, [property]: [curr] };
}, {});
// Convert the object
const groupsArr = Object.keys(groupsObj).map((property) => ({
[key]: property,
[itemsName]: groupsObj[property],
}));
return groupsArr;
}
Cevap #1
Benim yaklaşımım, groupBy()'ye çağrı imzasını vermek olurdu
function groupBy<T extends Record<K, string>, K extends keyof T, P extends PropertyKey>(
key: K, items: T[], itemsName: P): Grouped<T, K, P>[] { /*...*/ }
burada Grouped şu şekilde tanımlanır
type Grouped<T extends object, K extends keyof T, P extends PropertyKey> =
{ [Q in K]: T[K] } & { [Q in P]: T[] };
groupBy() yönteminin dönüş türünün argüman türlerine bağlı olabilmesi için, takip etmeniz gereken her türde genel (generic) olması gerekir. Burada onu üç tür parametresinde genel yaptım. items dizisinin eleman türü olan T, key argümanının türü olan K ve itemsName argümanının türü olan P vardır.
Bu tür parametreleri yalnızca geçerli girdilerle sınırlamak önemlidir. Örneğin, K'nın T'nin anahtarlarından biri olduğundan emin olmak istiyoruz; bu yüzden keyof tür operatörü kullanarak K extends keyof T ifadesini kullanıyoruz. Ayrıca T'nin K özelliğinde bir string değeri olduğundan emin olmak istiyoruz, böylece bunu uygulamanızın içinde groupsObj'nin bir indeks değeri olarak kullanabilirsiniz (ben groupsObj'nin, aramak zorunda olduğunuz bir diziye kıyasla daha kullanışlı bir çıktı olduğunu düşünüyorum, ama bu sizin tercihinize bağlı); bu yüzden Record<K, string> yardımcı türünü kullanarak T extends Record<K, string> ifadesini kullanıyoruz. Ve son olarak, P'nin bazı anahtar benzeri bir tür olduğundan emin olmak istiyoruz; bu yüzden PropertyKey türünü kullanarak P extends PropertyKey ifadesini kullanıyoruz.
Yani bu, girdiyi hallediyor... ya çıktı ne olacak? Grouped<T, K, P> tipi, döndürülen dizinin eleman tipi olarak tasarlanmıştır. Bu, Record<K, T[K]> & Record<P, T[]> ile eşdeğerdir. Bu, K anahtarına (yani key ile aynı) sahip bir özelliğe sahip olduğu anlamına gelir ve bu özelliğin değeri T tipi ile K anahtarında olan değerin aynısıdır (T[K] ile gösterilen indekslenmiş erişim tipi ile), ve (kesişim & ile temsil edilir) P anahtarına (yani itemsName ile aynı) sahip bir özelliğe sahiptir ve bu özelliğin değeri T'nin bir dizisidir (items ile aynı tip).
Bunu kullanabileceğimizden emin olalım:
interface Car {
make: string;
model: string;
year: string;
}
const cars: Car[] = [/*...*/]
const ret = groupBy("year", cars, "cars");
/* const ret: Grouped<Car, "year", "cars">[] */
const str = ret.map(
v => v.year + ": " + v.cars.map(
//^ ^^^^-- (property) year: string
//|--- (parameter) v: Grouped<Car, "year", "cars">
c => c.make + " " + c.model
// ^ <-- (parameter) c: Car
).join(", ")
).join("\n");
Yani ret türü Grouped<Car, "year", "cars">[] yani string türünde bir year özelliğine ve Car[] türünde bir cars özelliğine sahip nesnelerden oluşan bir dizi demektir. Bu yüzden çağıran taraf açısından iyi çalışır.
Uygulamaya gelince, derleyiciye bunun o çağrı imzasına uyduğunu hatırlatmak/ikna etmek/zorlamak için bazı yerlerde türleri açıklamanız ve belirtmeniz gerekir. İşte bunu yapmanın bir yolu:
function groupBy<T extends Record<K, string>, K extends keyof T, P extends PropertyKey>(
key: K, items: T[], itemsName: P): Grouped<T, K, P>[] {
const groupsObj = items.reduce<Record<string, T[]>>((acc, curr) => {
const property: string | number = curr[key];
if (property in acc) {
return { ...acc, [property]: [...acc[property], curr] };
}
return { ...acc, [property]: [curr] };
}, {});
const groupsArr = Object.keys(groupsObj).map((property) => ({
[key]: property,
[itemsName]: groupsObj[property],
}) as Grouped<T, K, P>);
return groupsArr;
}
Derleyiciye, reduce() dizi metoduna geçirilen başlangıç nesnesi {}'nin Record<string, T[]> türünde olduğunu söylememiz gerekiyor, böylece hangi anahtar olursa olsun, sahip olduğu özellik değerlerine T dizilerini yerleştireceğinizi anlayabilir.
Ve sadece şunu belirlememiz gerekiyor: {[key]: property, [itemsName]: groupsObj[property]} tür olarak Grouped<T, K, P> çünkü anahtarları generik olan hesaplanmış özelliklere güçlü türler vermeyi anlamıyor (detaylar için başka bir soruya verilen bu cevaba bakın).
Şimdi her şey derlendiğine göre ve umarım ihtiyacınız olduğu gibi çalışıyor.
Koda yönelik oyun alanı bağlantısı
