React中为API返回的深层嵌套对象定义TypeScript接口解决TS2339报错
关于嵌套类型定义的正确性说明
你直接写嵌套结构的接口,是完全符合TS语法规范的正确写法,没有任何问题。
- TS 从来没有要求嵌套结构必须拆成独立接口才能用,你可以把任意层级的对象结构直接写在类型的属性值位置,不需要额外抽离定义。
- 官方示例里拆分独立接口的操作,只是一种可选的维护优化,不是强制要求:拆分的核心价值是当某一层结构需要在多个组件复用,或者某一层结构会独立迭代时(比如未来quote字段下要新增EUR、CNY等更多币种),可以减少重复代码、缩小改动范围。如果当前的接口返回结构固定、只在这一处使用,直接把嵌套结构写在一个类型里反而更简洁,完全没必要为了“贴合官方示例”硬拆成4个接口。
原报错的核心原因
你遇到的Property 'data' does not exist on type '{}'报错,和“有没有拆接口”完全没有关系,根源是两个操作的疏漏:
- 给
useState初始化传空对象{}时,TS会自动把这个state的类型推导成“不存在任何属性的空对象类型{}”——哪怕你后续自己定义了完整的CurrencyQuote接口,只要没有把它作为泛型显式传给useState,TS根本不会把这个state和你的接口定义关联起来。 currencyQuote从一开始就不是有完整嵌套结构的值:它是用户提交表单、接口请求成功之后才会被赋值的,因此类型上必须把“初始无值”的状态纳入定义范围,不能直接要求它从组件加载开始就必须带全所有嵌套属性。
组件各变量的正确类型标注方案
结合上面的问题,不需要拆分嵌套接口,也可以把所有类型补全,同时修正原代码里的运行时逻辑问题:
- 显式给
currencyQuote指定泛型为CurrencyQuote | null,初始值设为null,替代原来的空对象{},这样TS会认可“这个状态可能是空,也可能是完整的嵌套报价结构”,可选链操作符的使用也和类型逻辑匹配。 - 对
fetch拿到的json返回值做类型断言,因为response.json()默认返回any,断言为我们定义的CurrencyQuote结构后,setCurrencyQuote的传值类型就完全匹配,不会出现隐式any的问题。 - 把异步拿到的领养成本值提升为独立的组件状态,解决原代码里“costToAdopt定义在handleSubmit内部、组件return部分访问不到”的作用域问题。
- 给表单提交事件补充类型,补上阻止默认刷新的逻辑,避免提交后页面刷新丢失状态。
修正后的完整代码
import { useState } from 'react'; // 不拆分接口,直接书写完整嵌套结构,TS完全支持 type CurrencyQuote = { data: { quote: { USD: { price: number } } } } export default function AnimalAdoptionCosts() { // 显式指定state类型:请求成功后是完整报价,初始状态为null const [currencyQuote, setCurrencyQuote] = useState<CurrencyQuote | null>(null); // userInput 初始为空字符串,TS自动推导为string,不需要额外标注 const [userInput, setUserInput] = useState(""); // 把领养成本作为独立状态,解决作用域访问问题 const [costToAdopt, setCostToAdopt] = useState<number | null>(null); const fetchAdoptionRates = async (animalType: string) => { const response = await fetch( `http://localhost:5000/v1/adoptionRate?animal=${animalType}` ); // 断言接口返回结构符合定义,同时兼容返回错误信息的场景 const body = await response.json() as CurrencyQuote & { message?: string }; if (response.status !== 200) throw Error(body.message ?? '接口请求失败'); setCurrencyQuote(body); }; const handleSubmit = async (e: React.FormEvent) => { // 阻止表单默认提交的页面刷新行为 e.preventDefault(); // 修正原代码里不存在的函数名问题,调用已定义的fetchAdoptionRates await fetchAdoptionRates(userInput); // 请求完成后再读取报价数据计算成本 if (currencyQuote) { const price = currencyQuote.data.quote.USD.price; setCostToAdopt(price); } }; return ( <> <form onSubmit={handleSubmit}> <label htmlFor="animal">Enter an animal type</label> <input value={userInput} id="userinput" placeholder="ex: Pomeranian" onChange={(event) => setUserInput(event.target.value)} /> <input type="submit" value="Submit" /> {costToAdopt && <p>领养成本:{costToAdopt} USD</p>} </form> </> ); }
内容的提问来源于stack exchange,提问作者stonerose036
相关产品推荐
相关产品推荐

