ReactJS实现数组重复食材合并与数量求和
合并重复食材并累加数量的实现方案
需求:遍历数组时合并重复食材(如示例中的切达奶酪),仅显示一次并累加其数量。现有代码可正常运行,但重复食材会多次显示,以下是修改方案:
现有代码
分类处理代码
const categorizedDataFoods12 = listUserM.reduce((acc, curr) => { const { id, Category, Foods } = curr; if (!acc[Category]) { acc[Category] = { items: [], }; } acc[Category].items.push({ id, Category, Foods }); return acc; }, {});
遍历渲染代码
{categorizedDataFoods12[key]?.items .map((checkbox, index) => { const data4food = checkbox.Foods const d4f = data4food.map((d) =>
示例数据源
{ "items": [ { "id": "biuBOoxw8Jxn9PPmyBKu", "Category": "LUNCH", "Foods": [ { "value": "Vita-Weat (pack)", "label": "Vita-Weat (pack)", "id": "ynBBDaX807LUa0093fJm", "Weight": "0.312", "Quantity": "0.50" }, { "Quantity": 1, "value": "Coppa (200g)", "Weight": "0.200", "id": "meyqfO8dxmNrx19EV86o", "label": "Coppa (200g)" }, { "id": "74b67090-15da-11ec-b278-b1ac8c1941a3", "Weight": 0.25, "label": "Cheddar Cheese (250g)", "Quantity": "0.50", "value": "Cheddar Cheese (250g)" }, { "value": "Cherry Tomatoes (punnet)", "label": "Cherry Tomatoes (punnet)", "Quantity": "0.50", "id": "74c64f10-15da-11ec-b278-b1ac8c1941a3", "Weight": 0.28 }, { "id": "74d5df70-15da-11ec-b278-b1ac8c1941a3", "label": "Lebanese Cucumbers", "Quantity": 1, "value": "Lebanese Cucumbers", "Weight": 0.17 }, { "value": "Pickled Onions", "id": "qSiC2ZR8v976QBzd0HTh", "Quantity": "3.00", "label": "Pickled Onions", "Weight": "" } ] }, { "id": "PodfQqRU3UOFKq4QX26q", "Category": "LUNCH", "Foods": [ { "id": "ynBBDaX807LUa0093fJm", "value": "Vita-Weat (pack)", "Weight": "0.312", "label": "Vita-Weat (pack)", "Quantity": "0.50" }, { "Weight": 0.25, "label": "Cheddar Cheese (250g)", "Quantity": "0.50", "id": "74b67090-15da-11ec-b278-b1ac8c1941a3", "value": "Cheddar Cheese (250g)" }, { "id": "74c64f10-15da-11ec-b278-b1ac8c1941a3", "label": "Cherry Tomatoes (punnet)", "Weight": 0.28, "Quantity": "0.50", "value": "Cherry Tomatoes (punnet)" }, { "Quantity": 1, "id": "74d5df70-15da-11ec-b278-b1ac8c1941a3", "value": "Lebanese Cucumbers", "Weight": 0.17, "label": "Lebanese Cucumbers" }, { "label": "Pickled Onions", "Weight": "", "id": "qSiC2ZR8v976QBzd0HTh", "value": "Pickled Onions", "Quantity": "3.00" } ] }, { "id": "ukZv9L19IHwIk4QRs0Aq", "Category": "LUNCH", "Foods": [ { "value": "Ham", "Weight": 0.176, "label": "Ham", "Quantity": "1.00" }, { "Weight": 0.59, "id": "76fd7a60-15da-11ec-b278-b1ac8c1941a3", "label": "Wraps (8 pack)", "Quantity": "0.25", "value": "Wraps (8 pack)" }, { "Quantity": "0.50", "label": "Cheddar Cheese (250g)", "id": "74b67090-15da-11ec-b278-b1ac8c1941a3", "Weight": 0.25, "value": "Cheddar Cheese (250g)" }, { "id": "74d5df70-15da-11ec-b278-b1ac8c1941a3", "Weight": 0.17, "value": "Lebanese Cucumbers", "label": "Lebanese Cucumbers", "Quantity": 1 }, { "Quantity": "0.50", "id": "74c64f10-15da-11ec-b278-b1ac8c1941a3", "value": "Cherry Tomatoes (punnet)", "Weight": 0.28, "label": "Cherry Tomatoes (punnet)" } ] }, { "id": "BGS8QUqy8bMoTgA61bgf", "Category": "LUNCH", "Foods": [ { "label": "Wraps (8 pack)", "value": "Wraps (8 pack)", "Quantity": "0.30", "id": "76fd7a60-15da-11ec-b278-b1ac8c1941a3", "Food": "", "Weight": 0.59 }, { "value": "Coppa (200g)", "Weight": "0.200", "Quantity": "0.25", "label": "Coppa (200g)", "id": "meyqfO8dxmNrx19EV86o" }, { "label": "Cheddar Cheese (250g)", "value": "Cheddar Cheese (250g)", "id": "74b67090-15da-11ec-b278-b1ac8c1941a3", "Weight": 0.25, "Quantity": "0.50" }, { "id": "74c64f10-15da-11ec-b278-b1ac8c1941a3", "Quantity": "0.50", "value": "Cherry Tomatoes (punnet)", "label": "Cherry Tomatoes (punnet)", "Weight": 0.28 }, { "id": "74d5df70-15da-11ec-b278-b1ac8c1941a3", "Quantity": 1, "value": "Lebanese Cucumbers", "Weight": 0.17, "label": "Lebanese Cucumbers" } ] } ] }
修改后的实现代码
分类合并食材的逻辑
const categorizedDataFoods12 = listUserM.reduce((acc, curr) => { const { Category, Foods } = curr; if (!acc[Category]) { acc[Category] = { items: {}, // 用对象按食材id分组存储合并数据 }; } Foods.forEach(food => { const foodId = food.id; // 转换Quantity为数字,避免字符串拼接错误 const quantity = parseFloat(food.Quantity) || 0; if (acc[Category].items[foodId]) { // 累加数量,保留两位小数 acc[Category].items[foodId].Quantity = (parseFloat(acc[Category].items[foodId].Quantity) + quantity).toFixed(2); } else { // 首次出现,复制原食材属性并格式化数量 acc[Category].items[foodId] = { ...food, Quantity: quantity.toFixed(2), }; } }); // 将分组对象转为数组,方便后续遍历渲染 acc[Category].items = Object.values(acc[Category].items); return acc; }, {});
简化后的遍历渲染
{categorizedDataFoods12[key]?.items.map((food, index) => ( <div key={food.id}> {food.label} - 累计数量: {food.Quantity} </div> ))}
关键说明
- 用食材的
id作为唯一标识,确保相同食材被正确合并(比label/value更可靠,避免名称差异导致的误判) - 统一将
Quantity转为数字计算,防止字符串拼接错误,最后用toFixed(2)保留两位小数 - 最终将分组对象转为数组,保持和原代码类似的遍历方式,无需大幅修改渲染逻辑
内容的提问来源于stack exchange,提问作者Paul VI
相关产品推荐
相关产品推荐

