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

React中splice删除数组指定项时UI始终删除最后一项问题

问题根因

列表渲染时使用数组索引i作为React元素的key是导致该异常的核心原因。
React的虚拟DOM diff算法依赖key值匹配组件实例、追踪状态变化。当你删除数组中间位置的元素后,后续元素的索引会依次前移,React会判定「最后一个索引对应的元素不存在」,直接卸载最后一个列表项的组件实例,保留前面的组件及其内部状态——哪怕你传入的props已经对应删除后的数据,组件内部状态(比如你用的BubbleEditor富文本组件、Checkbox的非受控状态)不会按预期更新,最终表现为数据正确但UI永远移除最后一项。

修复步骤
  • 为每个列表项生成稳定、全局唯一的标识,不要用随数组位置变化的索引当key
  • 替换所有列表渲染位置的key值为该唯一标识
  • 移除冗余的无效key设置

代码修改

1. 调整状态定义与新增逻辑,给每个项加唯一id

// 初始项增加唯一id,用时间戳即可满足简单场景需求,复杂场景可使用uuid
const [inputList, setInputList] = useState([
  { id: Date.now(), answer: "", isCorrect: false, points: 0 },
]);

const handleAddClick = () => {
  setInputList([
    ...inputList, 
    // 新增项同步生成唯一id,叠加随机数避免快速点击时同一毫秒生成重复id
    { id: Date.now() + Math.random(), answer: "", isCorrect: false, points: 0 }
  ]);
};

注:原有删除逻辑handleRemoveClick的实现是正确的,不需要调整。

2. 修改列表渲染的key值

只需要调整JSX里的key配置,其余业务逻辑保持不变即可:

<FormControl style={{ display: "flex", flex: 1 }}>
  <FormGroup>
    {inputList.map((x, i) => {
      return (
        {/* 把原来的key={i}替换为稳定唯一的x.id */}
        <div key={x.id}>
          <div className={styles.options}>
            {!distributePoints ? (
              <FormControlLabel
                {/* 删除原来多余的key={i},FormControlLabel不是map直接遍历的同级列表项,不需要设置key */}
                value={x.isCorrect}
                control={<Checkbox />}
                checked={x.isCorrect}
                onChange={(e) => handleIsCorrect(e, i)}
              />
            ) : (
              ""
            )}
            <div className="editor">
              <BubbleEditor
                handleAnswer={handleInputChange}
                index={i}
                theme="bubble"
              />
            </div>
            {distributePoints ? (
              <TextField
                variant="standard"
                name="points"
                InputProps={{
                  disableUnderline: true,
                  type: "number",
                }}
                value={x.points}
                onChange={(e) => handlePointChange(e, i)}
                className={styles.inputCounter}
              />
            ) : (
              ""
            )}

            {inputList.length !== 1 && (
              <IconButton
                onClick={() => handleRemoveClick(i)}
                className={styles.icon}
              >
                <DeleteIcon
                  sx={{ fontSize: 24, color: "#3699FF" }}
                />
              </IconButton>
            )}
          </div>
          {inputList.length - 1 === i && (
            <div
              style={{
                display: "flex",
                justifyContent: "space-between",
              }}
            >
              <button
                onClick={handleAddClick}
                type="button"
                className={styles.addButton}
              >
                <img src={PlusSign} alt="" /> Add another answer
              </button>

              <div className={styles.label}>
                <Checkbox
                  checked={shuffle}
                  onChange={handleShuffle}
                  style={{ color: "#00A3FF" }}
                />
                Shuffle answer
              </div>
            </div>
          )}
        </div>
      );
    })}
  </FormGroup>
</FormControl>
注意事项
  • 业务逻辑中传递索引i给处理函数做参数是完全可行的,只要不将索引用作key即可。
  • 仅当列表完全静态、不存在增删、排序、过滤操作时,才可以使用数组索引作为key。
  • 列表内部嵌套的子元素不需要重复设置key属性,只有map遍历生成的同级最外层元素需要配置合法key。

内容的提问来源于stack exchange,提问作者Tanjil Hossain

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 15:27:16