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

如何在Material UI中实现跨组件拖拽TableItem和ListItem?

嘿,我来帮你搞定Material UI里这两个拖拽需求!其实实现起来并不复杂,咱们分场景一步步来~

Material UI 跨组件拖拽实现方案

一、TableItem 跨组件拖拽

要实现TableItem从一个组件拖到另一个,我们可以结合react-dnd(React生态最常用的拖拽工具库)来实现,步骤如下:

1. 安装依赖

先装两个必备库:

npm install react-dnd react-dnd-html5-backend

2. 全局注入拖拽上下文

在你的App根组件里用DndProvider包裹所有需要拖拽的组件,提供HTML5拖拽能力:

import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';

function App() {
  return (
    <DndProvider backend={HTML5Backend}>
      {/* 你的所有业务组件都放这里 */}
    </DndProvider>
  );
}

3. 封装可拖拽的TableItem

给TableItem添加拖拽逻辑,让它能被拖起来:

import { useDrag } from 'react-dnd';
import { TableRow, TableCell } from '@mui/material';

const DraggableTableItem = ({ item, index }) => {
  // useDrag钩子返回拖拽状态和绑定元素的ref
  const [{ isDragging }, dragRef] = useDrag({
    type: 'TABLE_ITEM', // 定义拖拽类型,方便目标组件识别
    item: { id: item.id, data: item, index }, // 拖拽时携带的数据
    collect: (monitor) => ({
      isDragging: monitor.isDragging(), // 获取当前是否在拖拽中
    }),
  });

  return (
    <TableRow
      ref={dragRef}
      style={{ 
        opacity: isDragging ? 0.5 : 1, // 拖拽时半透明
        cursor: 'grab' // 鼠标悬停显示拖拽光标
      }}
    >
      <TableCell>{item.name}</TableCell>
      <TableCell>{item.value}</TableCell>
      {/* 其他单元格内容 */}
    </TableRow>
  );
};

4. 封装接收拖拽的目标组件

比如你要把TableItem拖到另一个自定义组件或Table里,给目标组件添加接收逻辑:

import { useDrop } from 'react-dnd';
import { Paper, Typography } from '@mui/material';

const DropTargetComponent = ({ onItemDrop }) => {
  const [{ isOver }, dropRef] = useDrop({
    accept: 'TABLE_ITEM', // 只接收指定类型的拖拽元素
    drop: (item) => {
      onItemDrop(item.data); // 拖拽完成后触发回调,处理数据
    },
    collect: (monitor) => ({
      isOver: monitor.isOver(), // 获取是否有元素悬停在上方
    }),
  });

  return (
    <Paper
      ref={dropRef}
      style={{
        padding: 20,
        marginTop: 20,
        backgroundColor: isOver ? '#f0f0f0' : '#fff',
        border: isOver ? '2px dashed #ccc' : '1px solid #eee',
      }}
    >
      <Typography variant="body1">
        {isOver ? '松开鼠标放入' : '拖拽TableItem到这里'}
      </Typography>
    </Paper>
  );
};

最后在父组件里处理数据的添加/移除逻辑就行啦~


二、ListItem 跨List组件拖拽

这个场景需要同时支持拖拽和接收,我们可以给ListItem同时绑定useDrag和useDrop钩子,实现跨List移动和同List排序:

1. 封装可拖拽可接收的ListItem

import React from 'react';
import { useDrag, useDrop } from 'react-dnd';
import { ListItem, ListItemText } from '@mui/material';

const DraggableListItem = ({ item, index, listId, onMoveItem }) => {
  const ref = React.useRef(null);

  // 拖拽逻辑
  const [{ isDragging }, dragRef] = useDrag({
    type: 'LIST_ITEM',
    item: { id: item.id, listId, index },
    collect: (monitor) => ({
      isDragging: monitor.isDragging(),
    }),
  });

  // 接收拖拽逻辑
  const [{ isOver }, dropRef] = useDrop({
    accept: 'LIST_ITEM',
    hover: (draggedItem) => {
      if (draggedItem.listId !== listId) {
        // 跨List移动:把元素从原List移到当前List
        onMoveItem(draggedItem, listId, index);
        draggedItem.listId = listId;
        draggedItem.index = index;
      } else if (draggedItem.index !== index) {
        // 同List内部排序
        onMoveItem(draggedItem, listId, index);
        draggedItem.index = index;
      }
    },
    collect: (monitor) => ({
      isOver: monitor.isOver(),
    }),
  });

  // 合并拖拽和接收的ref
  const combinedRef = (node) => {
    dragRef(node);
    dropRef(node);
    ref.current = node;
  };

  return (
    <ListItem
      ref={combinedRef}
      style={{
        opacity: isDragging ? 0.5 : 1,
        cursor: 'grab',
        backgroundColor: isOver ? '#f5f5f5' : 'transparent',
      }}
    >
      <ListItemText primary={item.title} />
    </ListItem>
  );
};

2. 封装可拖拽的List组件

import { List, Typography } from '@mui/material';

const DraggableList = ({ listId, items, onMoveItem }) => {
  return (
    <div style={{ margin: '0 20px', minWidth: 200 }}>
      <Typography variant="h6" gutterBottom>
        {listId}
      </Typography>
      <List>
        {items.map((item, index) => (
          <DraggableListItem
            key={item.id}
            item={item}
            index={index}
            listId={listId}
            onMoveItem={onMoveItem}
          />
        ))}
      </List>
    </div>
  );
};

3. 父组件处理数据更新

import React from 'react';

const ParentComponent = () => {
  // 初始化两个List的数据源
  const [lists, setLists] = React.useState({
    待办事项: [{ id: 1, title: '写代码' }, { id: 2, title: '测试' }],
    已完成: [{ id: 3, title: '需求评审' }, { id: 4, title: 'UI设计' }],
  });

  const handleMoveItem = (draggedItem, targetListId, targetIndex) => {
    // 1. 从原列表移除拖拽元素
    const sourceList = lists[draggedItem.listId].filter(
      (item) => item.id !== draggedItem.id
    );
    // 2. 把元素插入到目标列表的指定位置
    const targetList = [...lists[targetListId]];
    targetList.splice(targetIndex, 0, lists[draggedItem.listId][draggedItem.index]);
    // 3. 更新状态
    setLists({
      ...lists,
      [draggedItem.listId]: sourceList,
      [targetListId]: targetList,
    });
  };

  return (
    <div style={{ display: 'flex', padding: 20 }}>
      <DraggableList
        listId="待办事项"
        items={lists['待办事项']}
        onMoveItem={handleMoveItem}
      />
      <DraggableList
        listId="已完成"
        items={lists['已完成']}
        onMoveItem={handleMoveItem}
      />
    </div>
  );
};

额外小提示

  • 如果不想用第三方库,Material UI的@mui/x-data-grid内置了拖拽排序,但跨组件拖拽还是得结合外部工具。
  • 可以通过DragLayer组件自定义拖拽预览样式,让交互更友好。
  • 记得处理边界情况,比如拖拽到空列表、拖拽到列表首尾等。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 06:31:48