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

React中基于dnd-kit与react-bootstrap实现跨容器拖拽(无需Tailwind)

跨容器拖拽解决方案(基于@dnd-kit + React Bootstrap)

问题分析

你的代码无法实现跨容器拖拽的核心原因:

  • 未区分拖拽元素的源容器和目标容器,盲目对两个数组执行arrayMove(该方法仅适合同数组内元素排序)
  • 跨容器拖拽需要完成「从源数组移除元素 + 向目标数组插入元素」的操作,而非同数组内的位置交换

修改后的完整代码

App.js

import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import Container from 'react-bootstrap/Container';
import {
  DndContext,
  closestCenter,
  KeyboardSensor,
  PointerSensor,
  useSensor,
  useSensors,
} from "@dnd-kit/core";
import {
  arrayMove,
  SortableContext,
  verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { useState } from 'react';
import { SortableItem } from './SortableItem';

const Box = ({ children }) => (
  <div style={{ border: '1px solid #ccc', padding: '10px', marginBottom: '20px', minHeight: '200px' }}>
    {children}
  </div>
);

function App() {
  const [list1, setList1] = useState(["Test1", "Test2", "Test3"]);
  const [list2, setList2] = useState(["Test4", "Test5", "Test6"]);

  // 配置传感器,确保拖拽交互正常
  const sensors = useSensors(
    useSensor(PointerSensor),
    useSensor(KeyboardSensor)
  );

  const handleDragEnd = (event) => {
    const { active, over } = event;
    if (!over) return; // 拖拽到容器外不处理
    if (active.id === over.id) return;

    // 判断元素所属容器
    const isActiveInList1 = list1.includes(active.id);
    const isOverInList1 = list1.includes(over.id);
    const isActiveInList2 = list2.includes(active.id);
    const isOverInList2 = list2.includes(over.id);

    // 情况1:同容器内排序(List1内部)
    if (isActiveInList1 && isOverInList1) {
      setList1(prev => arrayMove(prev, prev.indexOf(active.id), prev.indexOf(over.id)));
      return;
    }

    // 情况2:同容器内排序(List2内部)
    if (isActiveInList2 && isOverInList2) {
      setList2(prev => arrayMove(prev, prev.indexOf(active.id), prev.indexOf(over.id)));
      return;
    }

    // 情况3:从List1拖到List2
    if (isActiveInList1 && isOverInList2) {
      // 从List1移除元素
      const newList1 = list1.filter(item => item !== active.id);
      // 在List2的over位置插入元素
      const overIndex = list2.indexOf(over.id);
      const newList2 = [...list2.slice(0, overIndex), active.id, ...list2.slice(overIndex)];
      setList1(newList1);
      setList2(newList2);
      return;
    }

    // 情况4:从List2拖到List1
    if (isActiveInList2 && isOverInList1) {
      const newList2 = list2.filter(item => item !== active.id);
      const overIndex = list1.indexOf(over.id);
      const newList1 = [...list1.slice(0, overIndex), active.id, ...list1.slice(overIndex)];
      setList2(newList2);
      setList1(newList1);
    }
  };

  return (
    <DndContext
      sensors={sensors}
      collisionDetection={closestCenter}
      onDragEnd={handleDragEnd}
    >
      <div style={{ display: 'flex', justifyContent: 'space-between', padding: '20px' }}>
        <Container className="p-3" style={{ width: "48%" }}>
          <h3 className="text-center mb-3">Primeiro</h3>
          <Box>
            <SortableContext
              items={list1}
              strategy={verticalListSortingStrategy}
            >
              {list1.map(item => <SortableItem key={item} id={item} />)}
            </SortableContext>
          </Box>
        </Container>
    
        <Container className="p-3" style={{ width: "48%" }}>
          <h3 className="text-center mb-3">Segundo</h3>
          <Box>
            <SortableContext
              items={list2}
              strategy={verticalListSortingStrategy}
            >
              {list2.map(item => <SortableItem key={item} id={item} />)}
            </SortableContext>
          </Box>
        </Container>
      </div>
    </DndContext>
  );
}

export default App;

SortableItem.js(必须正确实现)

import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";

export const SortableItem = ({ id }) => {
  const {
    attributes,
    listeners,
    setNodeRef,
    transform,
    transition,
    isDragging,
  } = useSortable({ id });

  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
    opacity: isDragging ? 0.5 : 1,
    padding: '10px',
    margin: '8px 0',
    border: '1px solid #007bff',
    borderRadius: '4px',
    backgroundColor: '#fff',
    cursor: 'grab',
  };

  return (
    <div
      ref={setNodeRef}
      style={style}
      {...attributes}
      {...listeners}
    >
      {id}
    </div>
  );
};

关键修改点说明

  1. 容器归属判断:通过includes方法判断拖拽元素(active)和目标位置元素(over)分别属于哪个列表,以此区分四种拖拽场景
  2. 跨容器数据更新:跨容器时先从源列表移除元素,再插入到目标列表的对应位置,而非使用arrayMove
  3. 完善传感器配置:添加PointerSensor和KeyboardSensor,确保鼠标和键盘拖拽都能正常触发
  4. 优化样式:给拖拽元素添加基础样式(边框、背景、拖拽时透明度变化),提升交互体验,完全不依赖Tailwind
  5. 边界处理:增加!over判断,避免拖拽到容器外时出现错误

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 09:54:53