React中在.map内调用setState引发无限循环的解决方法
解决showDiscounted函数引发的无限循环问题
问题根源
你在组件渲染流程中调用了showDiscounted函数,而该函数内部直接调用setTotal更新状态——组件渲染→触发showDiscounted执行→更新total状态→触发组件重新渲染→再次调用showDiscounted,形成闭环,导致无限循环。
最佳解决方案:使用派生状态(优先推荐)
由于total的值完全由parts、discount、option这些现有数据推导而来,不需要单独维护为状态。直接在渲染时计算total,既避免循环,又减少状态同步的麻烦。
修改步骤:
- 删除冗余的
total和discountedTotal状态(后者在代码中未实际使用) - 编写计算
total的函数,在渲染时直接调用 - 修改
showDiscounted,仅返回折扣后价格,移除内部状态更新逻辑
修改后的核心代码:
import { useState } from "react"; const QuoteCalcBot = ({ parts, setParts, option, discount }) => { //change individual discount for a part const handleDiscountChange = (part, e) => { console.log(e.target.value); console.log(part); } // 仅计算并返回单个零件的折扣后月付价格 const showDiscounted = (price) => { const temp = Math.ceil((price * ((100 - discount) / 100)) / 36) return ( <div className="col-2"> {temp} </div> ) } const addToTotal = (price) => { return Math.ceil(price / 36) } // 计算各列的折扣后总月付 const calculateColumnTotals = () => { const totals = [0, 0, 0]; parts.forEach(part => { part.active.forEach((isActive, index) => { if (isActive) { const monthlyDiscounted = Math.ceil((part.price * ((100 - discount) / 100)) / 36); totals[index] += monthlyDiscounted; } }); }); return totals; } const showParts = (activeIndex) => { return parts.map( (part) => part.active[activeIndex] && ( <div key={part.bodyPart} className="row"> <div className="col-3">{part.bodyPart}</div> <div className="col-2 text-decoration-line-through">${addToTotal(part.price)}</div> <div className="col-2"> ${showDiscounted(part.price)} </div> <div className="col-auto"> <select className="form-select-sm" aria-label="Default select example" onChange={(e) => handleDiscountChange(part, e)}> <option defaultValue>{discount}</option> <option value="30">30%</option> <option value="40">40%</option> <option value="50">50%</option> <option value="60">60%</option> </select> </div> <div className="col-1 text-end"><button type="button" className="btn-close" aria-label="Close"></button></div> </div>) ) } const columnTotals = calculateColumnTotals(); return ( <div className=""> <div className="row rows-cols-3"> <div className="col-4"> {showParts(0)} </div> <div className="col-4"> {showParts(1)} </div> <div className="col-4"> {showParts(2)} </div> {/* 使用计算出的总价格 */} <div className="col-4">discounted total for 36 months: {columnTotals[0]}</div> <div className="col-4">discounted total for 36 months: {columnTotals[1]}</div> <div className="col-4">discounted total for 36 months: {columnTotals[2]}</div> </div> </div> ); } export default QuoteCalcBot;
备选方案:使用useEffect维护状态
如果一定要将total作为状态维护,可通过useEffect在依赖项变化时更新状态,避免在渲染阶段触发状态更新:
- 保留
total状态,移除showDiscounted内的setTotal调用 - 添加
useEffect监听依赖项变化,计算并更新total
核心代码片段:
import { useEffect, useState } from "react"; const QuoteCalcBot = ({ parts, setParts, option, discount }) => { const [total, setTotal] = useState([0,0,0]); // ... 其他函数保持不变,仅修改showDiscounted和添加useEffect ... const showDiscounted = (price) => { const temp = Math.ceil((price * ((100 - discount) / 100)) / 36) return ( <div className="col-2"> {temp} </div> ) } useEffect(() => { const newTotal = [0, 0, 0]; parts.forEach(part => { part.active.forEach((isActive, index) => { if (isActive) { const monthlyDiscounted = Math.ceil((part.price * ((100 - discount) / 100)) / 36); newTotal[index] += monthlyDiscounted; } }); }); setTotal(newTotal); }, [parts, discount]); // 仅当依赖项变化时执行更新 // ... 其余代码不变 ... }
关键说明
- 派生状态方案更简洁,避免了状态同步的潜在问题,是React官方推荐的做法
useEffect方案适合必须将total作为状态的场景,需确保依赖项列表完整,避免遗漏导致数据不同步
内容的提问来源于stack exchange,提问作者Spork
相关产品推荐
相关产品推荐

