I'm trying to create a function with generic type in typescript. I have these function whose do the same
const convertBeverages = (beveragesCollection: BeveragesQuery): Beverage[] => {
let beverages: Beverage[] = [];
if (beveragesCollection) {
beverages =
beveragesCollection.data.map(({ id, attributes }) => {
const restaurant =
attributes?.restaurant?.reduce((acc, currentValue) => {
const restaurantId = currentValue?.restaurant?.data?.id ?? "-1";
return currentValue && currentValue.available
? [...acc, { price: currentValue.price, restaurantId }]
: acc;
}, [] as PriceRestaurant[]) ?? [];
const beverage = {
id: id ?? "",
name: attributes?.name ?? "",
position: attributes?.position ?? 0,
restaurant,
};
return beverage;
}) ?? [];
}
return beverages;
};
const convertDesserts = (dessertCollection: DessertsQuery): Dessert[] => {
let desserts: Dessert[] = [];
if (dessertCollection) {
desserts =
dessertCollection.data.map(({ id, attributes }) => {
const restaurant =
attributes?.restaurant?.reduce((acc, currentValue) => {
const restaurantId = currentValue?.restaurant?.data?.id ?? "-1";
return currentValue && currentValue.available
? [...acc, { price: currentValue.price, restaurantId }]
: acc;
}, [] as PriceRestaurant[]) ?? [];
const dessert: Dessert = {
id: id ?? "",
name: attributes?.name ?? "",
position: attributes?.position ?? 0,
restaurant,
};
return dessert;
}) ?? [];
}
return desserts;
};
So i want to create a generic function but i have an error and i don't know why.
Basically , i copied the previous function and I added the TData
with the input for the previous functions and the TModel
for the value of return
CodePudding user response:
Here's how you fix the issue:
// A simplified example
type A = {
data: string
}
type B = {
data: number
}
function test1<TData extends A | B>(collection: TData) {
// compiles
console.log(collection.data) // type is 'number | string'
}
What you're trying to do is add a constraint to your generic type.
The extends
keyword is how you add generic constraints (See https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-constraints).
TL:DR: Replace the =
with extends
.
CodePudding user response:
Firstly, replace TData = BeveragesQuery | DessertsQuery
with TData extends BeveragesQuery | DessertsQuery
. Using the equals sign you just give the default value to the parameter, but don't constrain it in any way, so one could call a function with convertSimpleModel<number, ...>
for example and it would make no sense to access property .data
on number
.
Secondly, you don't need the TModel
parameter, if you leave it, I could call convertSimpleModel<BeveragesQuery, Dessert>
and the function would be supposed to convert beverages to desserts which doesn't make sense either. Or even worse something like convertSimpleModel<DessertsQuery, number & string>
. Instead use conditional type BeveragesQuery extends TData ? Beverage : Dessert
where you would put TModel
otherwise. So
function convertSimpleModel<TData extends BeveragesQuery | DessertsQuery>(
collection: TData
): BeveragesQuery extends TData ? Beverage[] : Dessert[] {...}
Notice, that this is still not perfect, for example I could call convertSimpleModel<BeveragesQuery | DessertsQuery>(...)
which will definitely lead to weird results. Also you probably won't be able to type the array modelElements
correctly without using type assertions. Maybe what you want is not generics, but function overloads
function convertSimpleModel(collection: BeveragesQuery): Beverage[]
function convertSimpleModel(collection: DessertsQuery): Dessert[]
function convertSimpleModel(collection: BeveragesQuery | DessertsQuery): (Beverage | Dessert)[] {
const modelElements: (Beverage | Dessert)[] = []
// ...
}