You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 11:18:03