背景和效果
需要做一个垂直拖动的无序列表。因项目中其他模块已经使用了 dnd-kit , 为保持一致,使用的也是 dnd-kit。效果如下:
可拖拽列表示例
资料
选型决策矩阵

代码
js
import React, { useState } from 'react';
import { DndContext, closestCenter, PointerSensor, useSensor } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
const mockData = [
{ id: "1", name: "项目一" },
{ id: "2", name: "项目二" },
{ id: "3", name: "项目三" },
{ id: "4", name: "项目四" },
{ id: "5", name: "项目五" },
{ id: "6", name: "项目六" },
]
const DragableList = () => {
const [items, setItems] = useState(mockData);
const sensor = useSensor(PointerSensor, {
activationConstraint: {
distance: 5,
},
});
// 可排序列表项组件
const SortableItem = ({ item }) => {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: item.id });
const style = {
transform: CSS.Transform.toString(transform), // 决定拖拽效果
transition,
opacity: isDragging ? 0.5 : 1,
cursor: 'grab',
padding: '12px',
margin: '8px 0',
backgroundColor: '#f8f9fa',
border: '1px solid #dee2e6',
borderRadius: '4px',
userSelect: 'none',
};
return (
<div
ref={setNodeRef}
style={style}
{...attributes}
{...listeners}
>
// 如果拖拽的模块更复杂,写在这个return里
{item.name}
</div>
);
};
const handleDragEnd = (event) => {
const { active, over } = event;
if (active.id !== over?.id) {
setItems((items) => {
const oldIndex = items.findIndex(item => item.id === active.id);
const newIndex = items.findIndex(item => item.id === over.id);
// 重新排列数组
const newItems = [...items];
const [movedItem] = newItems.splice(oldIndex, 1);
newItems.splice(newIndex, 0, movedItem);
return newItems;
});
}
};
return (
<div style={{ padding: '20px', maxWidth: '400px', margin: '0 auto' }}>
<h2>可拖拽列表</h2>
<DndContext
sensors={[sensor]}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<SortableContext
items={items.map((item) => item.id)}
strategy={verticalListSortingStrategy}
>
{items.map((item) => (
<SortableItem key={item.id} item={item} />
))}
</SortableContext>
</DndContext>
<div style={{ marginTop: '20px', fontSize: '14px', color: '#6c757d' }}>
<p>拖拽说明:</p>
<ul>
<li>点击并拖动项目可重新排序</li>
<li>当前项目数: {items.length}</li>
</ul>
</div>
</div>
);
};
export default DragableList;
复杂组件
js
// 可排序列表项组件
const SortableItem = ({ item }) => {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: item.id });
const style = {
transform: CSS.Transform.toString(transform), // 决定拖拽效果
transition,
opacity: isDragging ? 0.5 : 1,
cursor: 'grab',
padding: '12px',
margin: '8px 0',
backgroundColor: '#f8f9fa',
border: '1px solid #dee2e6',
borderRadius: '4px',
userSelect: 'none',
};
return (
<div
ref={setNodeRef}
style={style}
{...attributes}
{...listeners}
>
<Tooltip>
<Popconfirm
title="确定删除这条属性吗?"
onConfirm={() => deleteAttr()}
conCancel={(e) => { console.log(e) }}
>
<Button>
删除
</Button>
</Popconfirm>
</Tooltip>
</div>
);
};