You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript类型兼容问题求助:Recipe接口与API数据适配困惑

问题分析与解决方案

核心问题是你混淆了API返回的原始数据类型和业务代码中处理后的数据类型,同时对TypeScript的数组/元组类型理解有误。

误区拆解

你最初的Recipe接口里,extendedIngredients用了元组类型([{ original: string; }]),这表示它是一个固定长度为1的数组,但Spoonacular API返回的是长度不固定的对象数组,所以会触发类型不匹配错误。

当你改成extendedIngredients: string[]后,TypeScript会认为info.extendedIngredients是字符串数组,每个元素都是string,自然没有original属性,因此报错'original' does not exist on type 'string'。

正确解决步骤

1. 定义匹配API返回的原始类型

首先明确API返回的extendedIngredients是对象数组,每个对象包含original字段,先定义准确的原始数据接口:

// 单个配料的原始类型
interface RecipeIngredient {
  original: string;
  // 若需要其他API返回的字段(如id、name),可在此添加
}

// API返回的完整Recipe原始类型
interface RawRecipe {
  id: number;
  title: string;
  image: string;
  servings: number;
  readyInMinutes: number;
  dairyFree: boolean;
  glutenFree: boolean;
  ketogenic: boolean;
  vegan: boolean;
  vegetarian: boolean;
  extendedIngredients: RecipeIngredient[]; // 这里是可变长度的对象数组,不是元组
}

2. 定义业务处理后的类型(可选)

如果希望业务代码中使用精简后的配料数组(仅存字符串),可单独定义业务类型:

interface ProcessedRecipe {
  id: number;
  title: string;
  image: string;
  servings: number;
  readyInMinutes: number;
  dairyFree: boolean;
  glutenFree: boolean;
  ketogenic: boolean;
  vegan: boolean;
  vegetarian: boolean;
  extendedIngredients: string[]; // 处理后的字符串数组
}

3. 调整请求与处理代码

区分原始数据和处理后数据,避免类型混淆:

useEffect(() => {
  const getRecipe = async () => {
    try {
      const res = await fetch(
        `https://api.spoonacular.com/recipes/${id}/information?apiKey=KEY&includeNutrition=false`
      );
      const rawData: RawRecipe = await res.json(); // 指定原始数据类型
      recipeInfo(rawData);
    } catch (err) {
      console.error(err);
    }
  };
  getRecipe();
}, [id]);

const recipeInfo = (info: RawRecipe) => {
  // 转换为业务处理后的类型
  const recipeData: ProcessedRecipe = {
    id: info.id,
    title: info.title,
    image: info.image,
    servings: info.servings,
    readyInMinutes: info.readyInMinutes,
    dairyFree: info.dairyFree,
    glutenFree: info.glutenFree,
    ketogenic: info.ketogenic,
    vegan: info.vegan,
    vegetarian: info.vegetarian,
    extendedIngredients: info.extendedIngredients.map(ing => ing.original), // 此时ing是RecipeIngredient类型,存在original属性
  };
  console.log(recipeData);
};

简化方案(无需区分双类型)

如果不需要保留原始配料对象,可直接在获取数据时转换并匹配业务接口:

interface Recipe {
  id: number;
  title: string;
  image: string;
  servings: number;
  readyInMinutes: number;
  dairyFree: boolean;
  glutenFree: boolean;
  ketogenic: boolean;
  vegan: boolean;
  vegetarian: boolean;
  extendedIngredients: string[];
}

useEffect(() => {
  const getRecipe = async () => {
    try {
      const res = await fetch(
        `https://api.spoonacular.com/recipes/${id}/information?apiKey=KEY&includeNutrition=false`
      );
      const rawData = await res.json();
      // 转换配料后赋值给Recipe类型
      const recipeData: Recipe = {
        ...rawData,
        extendedIngredients: rawData.extendedIngredients.map((ing: RecipeIngredient) => ing.original)
      };
      console.log(recipeData);
    } catch (err) {
      console.error(err);
    }
  };
  getRecipe();
}, [id]);

关键知识点总结

  • 元组vs数组:[{original: string}]是固定长度的元组,API返回的可变长度数组应使用RecipeIngredient[]。
  • 类型分离:API原始数据类型和业务处理后类型建议分开定义,避免类型混淆。
  • 类型安全:尽量避免使用any,通过定义明确的接口保证类型检查有效性。

内容的提问来源于stack exchange,提问作者Neelam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 07:42:42