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

React删除列表项时useRef获取id异常 始终删除最后一项

问题根源
  • useRef使用方式错误:单个useRef实例只能存储一个DOM节点引用,你把同一个mcqRef绑定到每道题的容器元素上,列表全部渲染完成后,mcqRef.current永远只会指向最后一个渲染的题目节点,不管点击哪个删除按钮,你拿到的都是最后一项的id,自然无法正确定位要删除的项。
  • id生成逻辑失效:你定义了ids状态,但新增题目时从未更新该状态的值,所有新生成的题目id都是初始值1,id重复导致filter筛选逻辑完全失效。
  • 违反React最佳实践:不要把<McqQuestion />这类React元素直接存储在state中,state应该只保存渲染所需的纯数据,React元素要放在JSX渲染阶段生成。
  • 列表渲染缺少唯一key:渲染题目列表时没有给每个列表项设置唯一的key属性,React复用DOM节点时会出现节点错位,表现为你看到的id错乱、项未被正确删除的异常。
  • HTML规范错误:所有删除按钮的label都设置了相同的id="remove",同一个HTML文档中id必须全局唯一。
修复方案

完全不需要通过useRef读取DOM属性拿id,直接在渲染时把当前项的id传入删除回调即可,从根源上避免DOM操作带来的问题,完整修正代码如下:

  1. 修正题目新增逻辑,保证id唯一,state仅存储纯数据
const [nextId, setNextId] = useState(1);
const [mcqAnswerList, setMcqAnswerList] = useState([]);

const handleCreateMcqQuestion = () => {
  // 用函数式更新避免闭包拿到旧的state值
  setMcqAnswerList(prev => [...prev, { id: nextId }]);
  // id自增,保证后续新增题目id全局唯一
  setNextId(prev => prev + 1);
};
  1. 修正删除逻辑,使用函数式更新避免闭包陷阱
const handleDeleteMcqQuestion = (targetId) => {
  setMcqAnswerList(prev => prev.filter(item => item.id !== targetId));
};
  1. 修正列表渲染逻辑,设置唯一key,直接传入当前项id,移除不必要的ref
{mcqAnswerList.map(questionItem => (
  // 给每个列表项设置唯一key,避免DOM复用错位
  <div key={questionItem.id}>
    <HStack spacing="24px" py={"9px"} data-id={questionItem.id}>
      <FormControl id={`type-${questionItem.id}`}>
        // 渲染时再生成React元素,不要存在state里
        <McqQuestion />
      </FormControl>
    </HStack>
    <div
      className="mx-[7px] w-[80px] cursor-pointer"
      // 直接传入当前项的id,不需要操作DOM拿值
      onClick={() => handleDeleteMcqQuestion(questionItem.id)}
    >
      // 重复的DOM id改用class,符合HTML规范
      <label className="remove-btn">
        <Remove />
      </label>
    </div>
  </div>
))}

提示:如果确实需要获取列表中多个项的DOM引用,应该使用ref回调函数存储ref数组,而不是用单个ref绑定所有项,但这个场景下完全不需要操作DOM,直接传参是最简单可靠的方案。

内容的提问来源于stack exchange,提问作者Ahmed Radi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 02:24:14