为何React中forEach结合useState第二次执行时修改了数组首个对象?
React严格模式下useState更新数组对象异常问题解析
问题场景
基于useState实现简易购物车逻辑,想要更新数组中name为surya的对象。在React严格模式下代码会执行两次,第一次输出结果正确,但第二次运行时数组索引0、原name为aditya的对象被修改成了surya。
示例代码
const [coba, setCoba] = useState([ { name: "aditya" }, { name: "joni" }, { name: "surya", money: 2000 }, ]); coba.forEach((item) => { let arr = []; if (item.name === "surya") { // 注:原代码中写为item.nama,属于拼写错误 item.money = 500000; arr.push(item); setCoba((prev) => { return [ { ...prev, ...arr, }, ]; }); } }); console.log(coba);
控制台输出
第一次执行结果:
[ { name: 'aditya' }, { name: 'joni' }, { name: 'surya', money: 500000 } ]
第二次执行结果:
[ { '0': { name: 'surya', money: 500000 }, '1': { name: 'joni' }, '2': { name: 'surya', money: 500000 } } ]
问题原因
直接修改原状态对象:循环中直接执行
item.money = 500000,属于对React状态的直接突变。React状态设计为不可变值,直接修改会污染原状态,在严格模式的二次执行中,被污染的状态会被复用,导致异常结果。错误的状态更新逻辑:
setCoba回调里返回的[{...prev, ...arr}]完全破坏了数组结构。原数组prev是带数字索引的类对象,arr是包含修改后对象的数组,两者展开合并成一个新对象后,再被包裹进数组,最终得到的不是正常数组,而是包含索引键的对象数组。严格模式二次执行时,状态已经变成这个异常结构,循环处理时自然会出现索引0被覆盖的问题。
正确实现方式
遵循React状态不可变原则,使用map创建新数组,修改目标对象时返回新副本:
import { useState, useEffect } from 'react'; const Cart = () => { const [coba, setCoba] = useState([ { name: "aditya" }, { name: "joni" }, { name: "surya", money: 2000 }, ]); // 用useEffect包裹,确保仅挂载时执行一次 useEffect(() => { setCoba(prevState => prevState.map(item => { if (item.name === "surya") { // 生成新对象,不修改原状态 return { ...item, money: 500000 }; } return item; })); }, []); console.log(coba); return <div>购物车示例</div>; }; export default Cart;
逻辑说明
- 使用
map遍历原数组,返回全新数组,保证状态不可变性。 - 找到目标对象时,通过
{...item}复制原对象属性,再修改money,避免污染原状态。 - 用
useEffect包裹更新逻辑,空依赖数组确保只在组件挂载时执行一次,避免无限循环和严格模式下的重复执行问题。
内容的提问来源于stack exchange,提问作者adityamms
相关产品推荐
相关产品推荐

