React中map循环调用API致无限重渲染及多实例状态处理方案
解决React组件无限重渲染并为每个order_item维护独立配送计数
问题背景
遍历order_item数组时,当item2.order_meal_deliveries.length > 0会直接调用fetchData函数,而该函数会更新全局的pendingDeliveries和totalDeliveries状态,触发组件重渲染,进而再次调用fetchData形成无限循环。同时当前状态无法区分不同order_item的配送数据,需要为每个项维护独立的待配送/总配送计数。
核心解决方案
将全局状态重构为以itemId为键的对象存储每个订单项的独立数据;用useEffect替代渲染阶段直接调用API,避免重渲染触发重复请求;用useCallback缓存API函数,减少不必要的组件更新。
步骤1:重构状态定义
把原来的两个独立状态合并为一个对象,每个键对应order_item的id,值存储该项的待配送和总配送计数:
const [deliveriesData, setDeliveriesData] = useState({});
步骤2:修改fetchData函数
更新状态时仅修改当前item对应的数据,保留其他项的状态,同时用useCallback缓存函数:
const fetchData = useCallback((orderId, itemId) => { api.ordersDelivery(orderId, itemId) .then((res) => { const responseData = res?.data?.response || {}; // 仅更新当前item的数据,不覆盖其他项 setDeliveriesData(prev => ({ ...prev, [itemId]: { pending: responseData.pending_delivery_count || 0, total: responseData.total_delivery_count || 0 } })); }) .catch((err) => console.error('获取配送数据失败:', err)); }, []);
步骤3:用useEffect触发API请求
不在渲染循环中直接调用fetchData,而是在组件挂载或依赖变化时,批量为符合条件的订单项发起请求,同时避免重复请求:
useEffect(() => { item.order_item.forEach(item2 => { // 仅当有配送记录且未获取过该item数据时发起请求 if (item2.order_meal_deliveries.length > 0 && !deliveriesData[item2.id]) { fetchData(item.id, item2.id); } }); }, [item, fetchData, deliveriesData]);
步骤4:调整渲染时的状态读取
在渲染对应订单项时,从deliveriesData中根据item2.id获取专属数据,例如进度条计算:
const getProgressValue = (itemId) => { const data = deliveriesData[itemId]; if (!data || data.total === 0) return "0%"; return `${Math.round(((data.total - data.pending) / data.total) * 100)}%`; }; // 渲染进度条时使用 <div className="progress"> <div className="progress-bar" role="progressbar" aria-valuemax="100" style={{ width: getProgressValue(item2.id) }} ></div> </div>
步骤5:优化列表Key值
将原来的key={index}替换为key={item2.id},确保列表项Key稳定,避免不必要的重渲染:
item.order_item.map((item2) => { // 渲染逻辑 }, []);
完整核心代码片段
状态与函数部分
import { useState, useEffect, useCallback } from 'react'; const YourComponent = ({ item }) => { const [deliveriesData, setDeliveriesData] = useState({}); const [startDate, setStartDate] = useState(null); const minDate = new Date(); const fetchData = useCallback((orderId, itemId) => { api.ordersDelivery(orderId, itemId) .then((res) => { const responseData = res?.data?.response || {}; setDeliveriesData(prev => ({ ...prev, [itemId]: { pending: responseData.pending_delivery_count || 0, total: responseData.total_delivery_count || 0 } })); }) .catch((err) => console.error('获取配送数据失败:', err)); }, []); useEffect(() => { item.order_item.forEach(item2 => { if (item2.order_meal_deliveries.length > 0 && !deliveriesData[item2.id]) { fetchData(item.id, item2.id); } }); }, [item, fetchData, deliveriesData]); const getProgressValue = (itemId) => { const data = deliveriesData[itemId]; if (!data || data.total === 0) return "0%"; return `${Math.round(((data.total - data.pending) / data.total) * 100)}%`; }; // 其他函数:createMealDelivery、handleViewMeal、handleRenewPlan
渲染部分
return ( <div> {item.order_item.map((item2) => { if (item2.order_meal_deliveries.length === 0) { return ( <div className="col-12 card card-md mb-4" key={item2.id}> {/* 未开始计划的原有渲染内容 */} </div> ); } else { return ( <div className="col-12 card card-md mb-4" key={item2.id}> <div className="col-auto"> <div className="row"> <div className="col-lg"> <div className="col-12 p-4"> {/* 原有订单信息、标题等内容 */} <div className="progress"> <div className="progress-bar" role="progressbar" aria-valuemax="100" style={{ width: getProgressValue(item2.id) }} ></div> </div> {/* 查看计划、续订计划按钮等内容 */} </div> </div> </div> </div> </div> ); } })} </div> ); };
内容的提问来源于stack exchange,提问作者Ashmit Qualwebs
相关产品推荐
相关产品推荐

