React父组件状态被子组件修改后不重新渲染问题排查
问题描述
父组件拥有本地状态,将setAdded函数传递给子组件ExtraItem。子组件调用该函数能修改父组件的added状态,但父组件不会触发重新渲染;只有在父组件内部修改状态时,才会触发重渲染,此时能看到子组件的修改结果,说明子组件确实修改了父状态,但子组件触发的状态变更无法触发父组件重渲染。此外,父组件中依赖added的useEffect在子组件触发状态变更时也不会执行。
父组件代码
const CustomizeItem = ({ item, i }) => { const [removed, setRemoved] = useState([]); const [added, setAdded] = useState([]); const [price, setPrice] = useState(item.price); const handleRemovable = (removable) => { if (removed.indexOf(removable) === -1) { setRemoved([...removed, removable]); } else { let removedCopy = [...removed]; removedCopy.splice(removed.indexOf(removable), 1); setRemoved(removedCopy); } }; useEffect(() => { console.log(added); console.log(price); }, [added, price]); return ( <div className="border-2 "> <p> Personaliza la {item.name} Nro {i + 1}: </p> <div> <p>Remover:</p> {item.removables.map((r, i) => { return ( <label> {r.name} <input name={r.name} type="checkbox" checked={null} onChange={() => handleRemovable(r)} /> </label> ); })} </div> <div className="flex flex-col"> <p>Añadir:</p> {item.extras.map((extra, i) => { return ( <ExtraItem key={i + 1} extra={extra} added={added} setAdded={setAdded} removed={removed} /> ); })} </div> <div> <p>Resumen: </p> <div> {g(item, "Un", "Una", "s")} <strong>"{item.name}"</strong> {removed.length > 0 && ( <p> {removed.map((r) => ( <p>✖️ Sin {r.name}</p> ))} </p> )} {added.length > 0 && ( <p> {added.map((a) => ( <p>✔️ Con extra de {a.name}</p> ))} </p> )} </div> </div> <p>Precio: ${price}</p> <button></button> </div> ); }; export default CustomizeItem;
子组件代码
const ExtraItem = ({ added, setAdded, extra }) => { const [count, setCount] = useState(0); const addExtra = (extra) => { let addedCopy = added; addedCopy.push(extra); setAdded(addedCopy); setCount(count + 1); }; const removeExtra = (extra) => { let addedCopy = added; addedCopy.splice(addedCopy.indexOf(extra), 1); setAdded(addedCopy); setCount(count - 1); }; return ( <div className="flex justify-between"> <p> {extra.name} - ${extra.price} </p> <div className="flex gap-2"> <IconButton variant="outlined" color="red" className="flex-none" size="sm" onClick={() => removeExtra(extra)} > <i class="fa-sharp fa-solid fa-minus" /> </IconButton> <Typography variant="h4" className="text-justify text-green-400"> {count} </Typography> <IconButton variant="outlined" color="green" className="flex-none" onClick={() => addExtra(extra)} size="sm" > <i className="fas fa-plus" /> </IconButton> </div> </div> ); }; export default ExtraItem;
问题原因
React的状态更新机制依赖引用变化判断状态是否改变。子组件中let addedCopy = added是直接赋值数组引用,并未创建新数组,后续的push或splice都是修改原数组内容。调用setAdded(addedCopy)时传递的是同一个数组引用,React会判定状态未发生变化,因此不会触发父组件重渲染,依赖added的useEffect也不会执行。
修复方案
修改子组件的addExtra和removeExtra函数,创建原数组的新副本(生成新引用),再修改副本并传递给setAdded:
const ExtraItem = ({ added, setAdded, extra }) => { const [count, setCount] = useState(0); const addExtra = (extra) => { // 创建新数组副本 const addedCopy = [...added]; addedCopy.push(extra); setAdded(addedCopy); setCount(prev => prev + 1); // 函数式更新避免闭包问题 }; const removeExtra = (extra) => { // 生成过滤后的新数组 const addedCopy = added.filter(item => item !== extra); setAdded(addedCopy); setCount(prev => prev - 1); // 函数式更新 }; return ( <div className="flex justify-between"> <p> {extra.name} - ${extra.price} </p> <div className="flex gap-2"> <IconButton variant="outlined" color="red" className="flex-none" size="sm" onClick={() => removeExtra(extra)} > <i class="fa-sharp fa-solid fa-minus" /> </IconButton> <Typography variant="h4" className="text-justify text-green-400"> {count} </Typography> <IconButton variant="outlined" color="green" className="flex-none" onClick={() => addExtra(extra)} size="sm" > <i className="fas fa-plus" /> </IconButton> </div> </div> ); }; export default ExtraItem;
更新count时使用函数式更新(setCount(prev => prev + 1)),可以避免因闭包导致的计数不准确问题。
内容的提问来源于stack exchange,提问作者Angel Orozco
相关产品推荐
相关产品推荐

