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> ); };
关键修改点说明
- 容器归属判断:通过
includes方法判断拖拽元素(active)和目标位置元素(over)分别属于哪个列表,以此区分四种拖拽场景 - 跨容器数据更新:跨容器时先从源列表移除元素,再插入到目标列表的对应位置,而非使用
arrayMove - 完善传感器配置:添加
PointerSensor和KeyboardSensor,确保鼠标和键盘拖拽都能正常触发 - 优化样式:给拖拽元素添加基础样式(边框、背景、拖拽时透明度变化),提升交互体验,完全不依赖Tailwind
- 边界处理:增加
!over判断,避免拖拽到容器外时出现错误
内容的提问来源于stack exchange,提问作者Marcello
相关产品推荐
相关产品推荐

