Next.js/React多层嵌套fetch拉取数据map渲染部分数据不显示
问题根因
- 循环发起子项数据请求时,每拿到一个接口返回就直接调用
setEvaluationResults更新状态,React状态更新为异步执行,后一次更新会直接覆盖前一次的内容,最终状态里只会保留最后一个返回请求的数据,前面拉取的内容会全部丢失。 - 当前
evaluationResults为单值存储结构,没有和evaluationOptions的subTitle做绑定映射,就算所有数据都成功返回,渲染时也无法将内容精准匹配到对应的标题块下,很容易出现串位、内容缺失的问题。 - 循环内的fetch为并行发起,接口返回顺序不固定,如果用数组按返回顺序存储数据,会出现A标题下渲染B内容的错位问题。
- 三层fetch嵌套的回调地狱写法没有统一错误捕获,任意一个接口报错都会导致后续逻辑全部中断,且问题定位难度高。
修复方案
- 调整状态初始值:将
evaluationResults的初始值设为空对象{},用于存储「subTitle -> 对应内容」的映射关系,查找效率高且不会出现内容错位。 - 替换循环内单次更新状态的逻辑:使用
Promise.all等待所有子项接口全部返回,将所有结果按subTitle组装为完整的映射对象后,再一次性调用setEvaluationResults更新状态,从根源避免状态被反复覆盖的问题。 - 渲染层做匹配关联:遍历
evaluationOptions时,直接用当前项的subTitle从映射对象中取对应内容渲染,同时增加简单的加载判断,避免数据未返回时读取属性抛出错误。 - 推荐使用async/await改写嵌套的then链,代码可读性更高,也方便统一添加错误处理。
修正后的数据拉取代码
const fetchAllEvaluationData = async () => { const evaluationId = localStorage.getItem("1317"); try { // 规则1:拉取评估基础信息 const evalBaseRes = await fetch(`http://localhost:4000/api/evaluations/search-by-id/${evaluationId}`); const evaluationDetails = await evalBaseRes.json(); setEvaluationData(evaluationDetails); // 规则2:拉取模板关联的subTitle列表 const optionRes = await fetch(`http://localhost:4000/api/options/get-all-option-by-title`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${AccessToken}` }, body: JSON.stringify({ title: evaluationDetails.template }) }); const subTitleList = await optionRes.json(); setEvaluationOptions(subTitleList); // 规则3:并行拉取所有subTitle对应的内容,全部返回后再统一更新状态 const resultMap = {}; await Promise.all( subTitleList.map(async (currentOption) => { const subDataRes = await fetch(`http://localhost:4000/api/evaluations-data/search-by-id-and-title`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${AccessToken}` }, body: JSON.stringify({ evaluationId: evaluationId, title: currentOption.subTitle }) }); const subContent = await subDataRes.json(); // 用subTitle作为key存储对应内容,从结构上避免错位 resultMap[currentOption.subTitle] = subContent; }) ); setEvaluationResults(resultMap); } catch (err) { console.error("评估数据拉取失败:", err); // 这里可以加自己的错误提示逻辑,比如弹toast } } // 在页面加载、evaluationId变化的时机调用该方法即可 fetchAllEvaluationData();
修正后的渲染代码
{ evaluationOptions.map((evalOptData) => { const currentContent = evaluationResults[evalOptData.subTitle]; return ( <> <b className="px-1 text-light bg-danger">{evalOptData.subTitle}</b> <Card.Body> <Row className="px-5"> {currentContent ? // 这里替换成你自己的内容渲染逻辑,根据接口返回的字段调整 <div>{currentContent.detail}</div> : "内容加载中..." } </Row> </Card.Body> </> ) }) }
内容的提问来源于stack exchange,提问作者Mark Willow Aldave
相关产品推荐
相关产品推荐

