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

React删除列表复选框项时下方项异常自动选中问题排查

问题描述

以下是我的组件实现效果:
组件效果截图

点击删除按钮后,被删除项下方的复选框会在从未被点击操作的情况下自动变为选中状态,属于不符合预期的异常行为:
异常效果截图

我是React新手,以下是父组件与子组件的完整实现代码,请问该异常产生的原因是什么,该如何修复?

TaskList.js

import { useState, useRef } from "react";
import Task from "./Task";
function TaskList({ className, tasks, setTasks }) {
  let inputRef = useRef();

  return (
    <div className={className}>
      <ul className="grow overflow-y-scroll">
        {tasks.map((task, i) => (
          <Task
            description={task.description}
            onChange={() => {
              task.checked = !task.checked;
            }}
          />
        ))}
      </ul>
      <input
        className="my-2 border-b-2 border-blue-300"
        placeholder="Enter task description..."
        ref={inputRef}
      ></input>
      <button
        className="rounded-md bg-blue-400 w-full p-4 text-white text-2xl"
        onClick={() => {
          setTasks([
            ...tasks,
            { description: inputRef.current.value, checked: false },
          ]);
        }}
      >
        Add
      </button>
      <button
        className="w-full bg-red-600 rounded-md p-2"
        onClick={() => {
          setTasks(tasks.filter((t) => !t.checked));
        }}
      >
        Delete
      </button>
    </div>
  );
}

export default TaskList;

Task.js

import React, { useState } from "react";

function Task({ description, onChange }) {
  return (
    <li className="flex flex-row my-2">
      <input className="mx-2" type="checkbox" onChange={onChange}></input>
      <span>{description}</span>
    </li>
  );
}
异常原因

异常由两个React基础使用错误共同导致:

  1. 列表渲染时没有给子项设置唯一稳定的key属性,React默认按数组索引复用DOM节点。删除前面的列表项后,后面的DOM节点会被错位复用,DOM本身保存的checkbox选中状态会直接带到下一个渲染的项上。
  2. 直接修改state对象的属性(代码里直接写task.checked = !task.checked),没有通过setTasks做不可变状态更新,React感知不到tasks状态的变化,不会主动同步checkbox的选中状态,UI完全依赖DOM自身的临时状态,进一步放大了DOM复用导致的错位问题。
修复方法
  • 给每个任务项生成唯一稳定的id,列表渲染时用id作为key,不要用数组索引当key
  • 所有对tasks的修改都走不可变更新,通过setTasks返回全新的状态数组,不要直接修改原state上的属性
  • 把checkbox改为受控组件,将checked状态从父组件传递给子组件,保证状态和UI完全对应

修复后的核心代码示例:

修复后的TaskList.js

import { useState, useRef } from "react";
import Task from "./Task";
function TaskList({ className, tasks, setTasks }) {
  let inputRef = useRef();

  return (
    <div className={className}>
      <ul className="grow overflow-y-scroll">
        {tasks.map((task) => (
          <Task
            key={task.id}
            description={task.description}
            checked={task.checked}
            onChange={() => {
              // 不可变更新选中状态
              setTasks(tasks.map(t => t.id === task.id ? {...t, checked: !t.checked} : t))
            }}
          />
        ))}
      </ul>
      <input
        className="my-2 border-b-2 border-blue-300"
        placeholder="Enter task description..."
        ref={inputRef}
      ></input>
      <button
        className="rounded-md bg-blue-400 w-full p-4 text-white text-2xl"
        onClick={() => {
          if(!inputRef.current.value.trim()) return
          setTasks([
            ...tasks,
            // 新增项时生成唯一id
            { id: Date.now() + Math.random(), description: inputRef.current.value.trim(), checked: false },
          ]);
          inputRef.current.value = ''
        }}
      >
        Add
      </button>
      <button
        className="w-full bg-red-600 rounded-md p-2"
        onClick={() => {
          setTasks(tasks.filter((t) => !t.checked));
        }}
      >
        Delete
      </button>
    </div>
  );
}

export default TaskList;

修复后的Task.js

import React from "react";

function Task({ description, checked, onChange }) {
  return (
    <li className="flex flex-row my-2">
      {/* 受控绑定checked状态 */}
      <input className="mx-2" type="checkbox" checked={checked} onChange={onChange}></input>
      <span>{description}</span>
    </li>
  );
}

export default Task;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 05:36:13