React JS中如何实现列表水平垂直双向拖拽排序功能?
双向换行拖拽排序列表实现方案
react-beautiful-dnd 原生仅支持单轴(纯垂直/纯水平)拖拽排序,没有内置网格/自动换行布局的多方向位置计算逻辑,跨行列拖拽时只会比对单轴坐标差,必然出现你遇到的拖拽位置错位、索引计算异常问题。且该库目前已停止维护,不建议自行修改源码适配双向拖拽,后续维护成本极高。
推荐实现方案(适配自动换行、无滚动条需求)
使用 @dnd-kit 实现,该库原生支持多方向排序、网格布局,无需自定义碰撞检测逻辑即可满足需求:
- 列表容器直接使用
flex-wrap: wrap布局即可,无需添加overflow: auto,列表项数量超出容器宽度时会自动换行,完全符合布局要求 - 内置矩形碰撞检测逻辑,会同时计算拖拽元素在水平、垂直两个方向的位置偏移,跨行列拖拽时索引计算准确,不会出现项拖到目标位置却无法正确占位的问题
- 支持触屏、键盘操作,无障碍访问能力完善,是目前React生态替代react-beautiful-dnd的主流方案
核心实现代码
import { useState } from 'react'; import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'; import { arrayMove, SortableContext, sortableKeyboardCoordinates, rectSortingStrategy, useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; // 单个可拖拽排序项 function SortableItem({ id, content }) { const { attributes, listeners, setNodeRef, transform, transition, } = useSortable({ id }); const itemStyle = { transform: CSS.Transform.toString(transform), transition, width: 120, height: 60, margin: 8, border: '1px solid #e5e7eb', borderRadius: 4, display: 'flex', alignItems: 'center', justifyContent: 'center' }; return ( <div ref={setNodeRef} style={itemStyle} {...attributes} {...listeners}> {content} </div> ); } // 拖拽排序列表容器 export default function BidirectionalDragList() { // 初始列表数据,可替换为实际业务数据 const [items, setItems] = useState( Array.from({ length: 12 }, (_, i) => ({ id: `item-${i+1}`, content: `Item${i+1}` })) ); const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 5 }, // 配置拖拽触发阈值,避免点击误触 }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }) ); const handleDragEnd = (event) => { const { active, over } = event; if (!over || active.id === over.id) return; setItems((prev) => { const oldIndex = prev.findIndex(item => item.id === active.id); const newIndex = prev.findIndex(item => item.id === over.id); return arrayMove(prev, oldIndex, newIndex); }); }; return ( <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd} > <SortableContext items={items.map(item => item.id)} // 必须传入rectSortingStrategy,适配换行/网格布局的双向排序 strategy={rectSortingStrategy} > {/* 容器开启flex-wrap,不设overflow属性,高度随内容撑开、项自动换行 */} <div style={{ display: 'flex', flexWrap: 'wrap' }}> {items.map(item => ( <SortableItem key={item.id} id={item.id} content={item.content} /> ))} </div> </SortableContext> </DndContext> ); }
关键注意点
- 不要给外层列表容器设置固定高度或
overflow属性,保持容器高度随内容自动撑开,项数量较多时自然换行,不会出现滚动条 SortableContext必须传入rectSortingStrategy作为排序策略,不要用默认的垂直/水平排序策略,否则还是会出现单轴计算的错位问题- 上述实现可直接复现预期效果:将换行后的Item9拖拽到上一行的Item7位置时,会正确计算目标索引,拖拽完成后Item9会占据Item7的原有位置,其余项自动顺移。
内容的提问来源于stack exchange,提问作者jashan singh
相关产品推荐
相关产品推荐

