React使用useState更新深层嵌套数组状态仅生效一次如何解决
问题根因
- 你的写法违反了React状态不可变的核心原则:直接原地修改了
prevState数组内的对象属性,没有生成新的对象引用。虽然你返回了展开后的新数组,但数组内对应索引的对象还是原来的引用,React的状态比对逻辑会认为该对象没有变化,导致后续更新失效。 - 额外隐患:通过
getAttribute拿到的索引是字符串类型,虽然JS数组支持字符串索引访问,但属于不规范写法,可能引发不可预期的问题。
修复后的代码
1. 基础修复方案(无需修改组件逻辑)
修改select函数,更新状态时全程不修改原状态,为被修改的元素生成新的对象引用:
const select = (e) => { // 把索引转成数字类型避免隐患 const i = Number(e.target.getAttribute('data-index')); setItems((prevState) => { // 先复制整个数组生成新引用 const newItems = [...prevState]; // 复制要修改的对象,更新属性后替换原位置元素 newItems[i] = { ...newItems[i], selected: !newItems[i].selected }; return newItems; }); };
2. 优化方案(推荐,避免DOM属性传参)
直接在事件绑定时传入数字索引,省略类型转换步骤,代码更简洁:
首先修改List组件内的事件绑定逻辑:
{items.map((item, i) => { return ( <tr className="list-table-tr"> {hasSelector ? ( <td className="list-table-td-selector"> {item.selected ? ( <div className="global-selector-selected" onClick={() => select(i)} ></div> ) : ( <div className="global-selector-unselected" onClick={() => select(i)} ></div> )} </td> ) : null}
对应的select函数简化为:
const select = (i) => { setItems((prevState) => { const newItems = [...prevState]; newItems[i] = { ...newItems[i], selected: !newItems[i].selected }; return newItems; }); };
补充说明
React状态更新要求所有状态修改都要生成新的引用,对于数组内的对象类型元素,仅展开数组是不够的,必须同时为被修改的元素生成新的对象引用,才能触发正常的重渲染流程。
内容的提问来源于stack exchange,提问作者Dawesign
相关产品推荐
相关产品推荐

