简体   繁体   中英

Creating custom return type for a generic function in TypeScript

I have the following generic function:

function getFoodProperties<T, K extends keyof T>(foods: T[], keys: K[]): any[] { // <-- any needs to be typed according to generic types?
  return foods.map((food) => {
    let obj: any = {}; // <-- this any also needs typing - it should be the same type as the one above I think?

    keys.forEach((key) => {
      obj[key] = food[key];
    });

    return obj;
  });
}

const foods: IFoods[] = [
  { name: "Beef", colour: "Red", weight: 8 },
  { name: "Chips", colour: "Yellow", weight: 1 },
  { name: "Lettuce", colour: "Green", weight: 9 },
];

getFoodProperties(foods, ["name", "colour"]);

I have it mostly typed I think, but I'm having trouble typing the return for the new object shape. The object keys are being picked according to the items in the getFoodProperties second argument. It's so far outputting the correct data, but it's not typed.

It seems without assigning a type to the let obj , it errors with:

Type 'K' cannot be used to index type '{}'

So I believe I need to create a generic type used for the function return value, and the inner object in the foods map, but I'm not sure how to approach it.

You almost have it already. Are you aware of the Pick helper type ? The Pick<T, K> creates a new object type where the keys K are 'picked' from the type T .

In your case, the correct return type should be Pick<T, K>[] , and this seems to give the inference you desire:

function getFoodProperties<T, K extends keyof T>(foods: T[], keys: K[]): Pick<T, K>[] {
  // ...
}

// This is now an array of objects with "name" and "colour" properties only.
const result = getFoodProperties(foods, ["name", "colour"]);

result.forEach(food => {
    food.name;
    food.colour;
    food.weight; // Property 'weight' does not exist on type 'Pick<IFoods, "name" | "colour">'
})

For the type of your inner variable obj , I would recommend just leaving it as any . You could use the same type, Pick<T, K> for that, but in that case TypeScript will require both the name and colour properties to be defined in the object right away, you cant assign them as a second step as you do now. If you absolutely want to avoid using any , then consider using Partial<Pick<T, K>> with a cast on the return line to change the type back to just Pick<T, K> :

function getFoodProperties<T, K extends keyof T>(foods: T[], keys: K[]): Pick<T, K>[] {
  return foods.map((food) => {
    let obj: Partial<Pick<T, K>> = {};

    keys.forEach((key) => {
      obj[key] = food[key];
    });

    return obj as Pick<T, K>;
  });
}

Playground Link

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM