【dnd-kit】react前端做一个可以垂直拖动的无序列表

背景和效果

需要做一个垂直拖动的无序列表。因项目中其他模块已经使用了 dnd-kit , 为保持一致,使用的也是 dnd-kit。效果如下:

可拖拽列表示例

资料

React生态中主流拖拽库的深度对比与选型指南

选型决策矩阵

代码

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>
  );
};