手把手教你用 React 实现可拖拽排序的 Ant Design 表格
一、前置知识准备
1.1 什么是高阶组件(HOC)?
高阶组件就像"组件包装纸",可以为现有组件添加额外功能。例如:
const EnhancedComponent = withHOC(BaseComponent);
react-sortable-hoc
的核心就是通过三个 HOC 实现拖拽功能
1.2 不可变数据(Immutable Data)
在 React 中,直接修改状态可能导致渲染问题。正确做法:
// 错误 ❌
const newArr = arr.splice(index, 1);
// 正确 ✅
const newArr = [...arr.slice(0, index), ...arr.slice(index+1)];
这也是使用 array-move
库的原因。
1.3 拖拽排序基本原理
- 监听鼠标/触摸事件
- 计算元素位置变化
- 更新数据顺序
- 重新渲染界面
二、核心实现详解
2.1 安装依赖
npm install react-sortable-hoc array-move
2.2 创建拖拽手柄组件
const DragHandle = SortableHandle(() => (
<MenuOutlined style={{
cursor: "grab",
padding: "0 8px",
opacity: 0.5
}} />
));
2.3 包裹表格组件
// 可拖拽的表格行
const SortableItem = SortableElement(
(props: React.HTMLAttributes<HTMLTableRowElement>) => <tr {...props} />
);
// 可拖拽的表格体
const SortableBody = SortableContainer(
(props: React.HTMLAttributes<HTMLTableSectionElement>) => <tbody {...props} />
);
2.4 状态管理与排序逻辑
const [dataSource, setDataSource] = useState(initialData);
const onSortEnd = ({ oldIndex, newIndex }: SortEnd) => {
if (oldIndex === newIndex) return;
const newData = arrayMoveImmutable(
dataSource,
oldIndex,
newIndex
);
setDataSource(newData);
};
2.5 完整组件集成
const DraggableTable = () => {
return (
<Table
components={{
body: {
wrapper: (props) => (
<SortableBody
useDragHandle
helperClass="dragging-row"
onSortEnd={onSortEnd}
{...props}
/>
),
row: ({ rowKey, ...rest }) => {
const index = dataSource.findIndex(
(item) => item.key === rowKey
);
return <SortableItem index={index} {...rest} />;
}
}
}}
rowKey="key"
columns={[
{
title: '拖拽',
render: () => <DragHandle />
},
// 其他列...
]}
dataSource={dataSource}
/>
);
};
三、关键问题解析
3.1 为什么需要 data-row-key?
Ant Design Table 会自动为每行注入 data-row-key
属性,其值等于 rowKey
属性配置的字段值。我们通过这个属性找到对应的数据索引。
3.2 拖拽动画实现原理
通过 helperClass
添加 CSS 动画:
.dragging-row {
background: #fafafa;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transform: rotate(2deg);
transition: transform 0.2s;
}
3.3 性能优化技巧
虚拟滚动:集成 react-virtualized
import { Table } from 'react-virtualized';
节流处理:对 onSortEnd 添加节流
import { throttle } from 'lodash';
const throttledSort = throttle(onSortEnd, 300);
四、横向对比:主流拖拽库选型
库名 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
react-sortable-hoc | 轻量简单,与 AntD 集成方便 | 功能较基础 | 简单表格/列表 |
react-beautiful-dnd | 动画流畅,支持嵌套拖拽 | 学习曲线陡峭 | 看板类复杂应用 |
dnd-kit | 现代化 API,TypeScript 友好 | 生态不够成熟 | 需要高度定制的项目 |
SortableJS | 不依赖 React,功能强大 | 需要手动集成 React | 多框架混合项目 |
五、扩展功能实现
5.1 跨表格拖拽
// 使用 react-dnd 实现
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
const App = () => (
<DndProvider backend={HTML5Backend}>
<Table1 />
<Table2 />
</DndProvider>
);
5.2 拖拽保存状态
// 每次排序后调用 API
const onSortEnd = async ({ oldIndex, newIndex }) => {
const newData = arrayMoveImmutable(...);
setDataSource(newData);
try {
await axios.post('/api/save-order', {
order: newData.map(item => item.id)
});
} catch (err) {
// 回滚操作
setDataSource(prevData);
}
};
六、最佳实践建议
- 唯一标识:确保 rowKey 始终唯一且稳定
- 错误边界:添加拖拽失败的恢复机制
- 移动端适配:
import { TouchBackend } from 'react-dnd-touch-backend';
<DndProvider backend={TouchBackend} options={{ enableMouseEvents: true }}>
- 无障碍支持:
<DragHandle
aria-label="拖拽手柄"
tabIndex={0}
/>
七、总结
通过本文,您已掌握:
✅ React 高阶组件原理
✅ 不可变数据操作
✅ 拖拽排序核心实现
✅ 性能优化技巧
✅ 主流拖拽库选型指南
在实际项目中,请根据具体需求选择合适方案。对于简单表格推荐使用 react-sortable-hoc
+ array-move
的组合,复杂场景可考虑 react-beautiful-dnd
。
附录:常见问题速查表
现象 | 可能原因 | 解决方案 |
---|---|---|
拖拽后数据错位 | rowKey 配置错误 | 检查数据唯一标识字段 |
拖拽时页面卡顿 | 大数据量未优化 | 添加虚拟滚动 |
移动端无法拖拽 | 未启用触摸事件支持 | 集成 react-dnd-touch-backend |
拖拽样式异常 | CSS 类名冲突 | 添加样式作用域隔离 |