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

React待办列表拖拽后state未更新,如何实现拖拽完成后状态同步?

解决方案

核心思路

你当前直接操作DOM实现拖拽的方式和React状态驱动视图的运行逻辑冲突,需要将拖拽行为转化为对tasks状态的修改,由状态变更自动驱动视图更新,无需手动操作DOM修改节点位置。

步骤1:调整拖拽信息存储逻辑

不要存储拖拽的DOM节点,改为存储被拖拽任务的ID和所属的原始分组:

// 替换原来的draggableTodo变量
let draggedTask = null;

const dragStart = (event) => {
  // 从任务节点的自定义属性读取信息
  const { taskId, sourceGroup } = event.target.dataset;
  draggedTask = {
    id: Number(taskId),
    sourceGroup: sourceGroup
  };
  // 可保留原有的隐藏样式逻辑,不影响状态更新
  setTimeout(() => {
    event.target.style.display = "none";
  }, 0);
};

步骤2:修改drop事件处理逻辑,实现状态更新

const dragDrop = (event) => {
  event.preventDefault();
  event.target.style.border = "none";
  // 获取目标分组(板块div的id和tasks的分组key对应)
  const targetGroup = event.currentTarget.id;
  // 拖拽到原分组无需更新
  if (draggedTask.sourceGroup === targetGroup) return;

  // 不可变更新state
  setTasks(prev => {
    // 浅拷贝所有分组
    const newGroups = {...prev};
    // 从原分组过滤掉被拖拽的任务
    const sourceList = newGroups[draggedTask.sourceGroup].filter(item => item._id !== draggedTask.id);
    // 找到被拖拽的任务对象
    const movedTask = newGroups[draggedTask.sourceGroup].find(item => item._id === draggedTask.id);
    // 把任务添加到目标分组
    const targetList = [...newGroups[targetGroup], movedTask];
    // 返回新的state
    return {
      ...newGroups,
      [draggedTask.sourceGroup]: sourceList,
      [targetGroup]: targetList
    };
  });
  // 删掉原来的appendChild逻辑,不需要手动操作DOM
};

步骤3:修改JSX结构,添加必要的自定义属性

  1. 给每个板块div的id改为和tasks的分组key完全对应:
{/* Todo板块 */}
<div
  id="todo" {/* 原来的id是todo_div,改成todo */}
  className="tasks-body_child"
  onDragOver={dragOver}
  onDragLeave={dragLeave}
  onDrop={dragDrop}
>
{/* In Progress板块 */}
<div
  id="inProgress" {/* 原来的id是inProgress_div,改成inProgress */}
  className="tasks-body_child "
  onDragOver={dragOver}
  onDragEnter={dragEnter}
  onDragLeave={dragLeave}
  onDrop={dragDrop}
>
{/* Done板块 */}
<div
  id="done" {/* 原来的id是done_div,改成done */}
  className="tasks-body_child"
  onDragOver={dragOver}
  onDragEnter={dragEnter}
  onDragLeave={dragLeave}
  onDrop={dragDrop}
>
  1. 给每个任务节点添加自定义属性,存储任务ID和所属分组:
{/* Todo分组的任务 */}
{tasks.todo.map((todo) => {
  return (
    <div
      key={todo._id}
      data-task-id={todo._id}
      data-source-group="todo"
      className="tasks-bd_content"
      draggable={true}
      onDragStart={dragStart}
      onDragEnd={dragEnd}
    >
      {todo.name}
    </div>
  );
})}

{/* In Progress分组的任务 */}
{tasks.inProgress.map((inprogress) => {
  return (
    <div
      key={inprogress._id}
      data-task-id={inprogress._id}
      data-source-group="inProgress"
      className="tasks-bd_content"
      draggable={true}
      onDragStart={dragStart}
      onDragEnd={dragEnd}
    >
      {inprogress.name}
    </div>
  );
})}

{/* Done分组的任务 */}
{tasks.done.map((done) => {
  return (
    <div
      key={done._id}
      data-task-id={done._id}
      data-source-group="done"
      className="tasks-bd_content"
      draggable={true}
      onDragStart={dragStart}
      onDragEnd={dragEnd}
    >
      {done.name}
    </div>
  );
})}

补充说明

dragEnd事件里的样式恢复逻辑可以保留,也可以直接删除,因为状态更新后React会重新渲染所有任务节点,原来的DOM节点修改会被覆盖。如果发现拖拽结束后样式异常,可以直接去掉dragStart里修改display的逻辑,改用CSS的opacity属性控制拖拽时的视觉效果,避免和React渲染冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 17:06:05