🌲系列一:跟着官方示例学习 @tanStack-table --- Basic
🌲系列二:跟着官方示例学习 @tanStack-table --- Header Groups
🌲系列三:跟着官方示例学习 @tanStack-table --- Column Filters
🌲系列四:跟着官方示例学习 @tanStack-table --- Column Ordering
🌲系列五:跟着官方示例学习 @tanStack-table --- Sticky Column Pinning
🌲系列六:跟着官方示例学习 @tanStack-table --- Column Sizing
🌲系列七:跟着官方示例学习 @tanStack-table --- Expanding
🌲系列八:跟着官方示例学习 @tanStack-table --- Pagination
还记得在之前的文章中,我们已经实现了列排序 跟着官方示例学习 @tanStack-table --- Column Ordering,今天我们就来实现让用户可以随心所欲地拖动表格的每一行,就像排乐高积木一样!
我们会用到 @dnd-kit
------ 一个强大又灵活的 React
拖拽库,和 TanStack Table
配合起来简直是天作之合!
🎛️ 为什么需要自定义 rowId
TanStack Table
默认使用数组索引作为 row.id
。这对一般场景没问题,但在拖拽排序中,索引会变化,导致状态错乱。
我们必须提供一个稳定的 ID
,比如:
tsx
useReactTable({
data,
columns,
getRowId: (row) => row.userId,
});
✅ 这是整个拖拽功能的基石。如果你忽略它,调试时就会遇到:"为什么我拖了,但数据没变?" 的迷之场景。
✨ 拖拽的三要素:传感器、排序策略和回调处理
在正式开始前,我们来看看我们需要哪些基础结构:
tsx
const sensors = useSensors(
useSensor(MouseSensor, {}),
useSensor(TouchSensor, {}),
useSensor(KeyboardSensor, {})
);
这是 dnd-kit
的"感知器"们,负责检测你是在鼠标拖、手指划、还是键盘控制。
🧲 拖拽把手 Cell
我们需要一个专属的拖动按钮------小小一枚 🟰
图标,它其实是个按钮,绑定了拖拽事件。
tsx
const RowDragHandleCell = ({ rowId }: { rowId: string }) => {
const { attributes, listeners } = useSortable({ id: rowId });
return <button {...attributes} {...listeners}>🟰</button>;
};
📌 这个组件会被插入到表格的第一列,用来当"拖动的起点"。
⌚️ 可拖拽的表格行 DraggableRow
每一行都要支持拖动,这时候我们就得使用 useSortable
给 <tr>
包一层魔法:
tsx
const DraggableRow = ({ row }: { row: Row<Person> }) => {
const { transform, transition, setNodeRef, isDragging } = useSortable({
id: row.original.userId,
});
const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.8 : 1,
zIndex: isDragging ? 1 : 0,
position: "relative",
};
return (
<tr ref={setNodeRef} style={style}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id} style={{ width: cell.column.getSize() }}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
);
};
当你开始拖拽时,isDragging
会变成 true
,我们可以让这行有些"飘起来"的视觉效果,视觉上的"提起感"来自于 opacity + zIndex + boxShadow
。
🔗 SortableContext 是桥梁
我们用 SortableContext
包住了所有 <tr>
:
tsx
<SortableContext items={dataIds} strategy={verticalListSortingStrategy}>
{table.getRowModel().rows.map((row) => (
<DraggableRow key={row.id} row={row} />
))}
</SortableContext>
这是 dnd-kit
的核心设计:告诉引擎,谁可以被排序、按什么方式排序。
-
items
: 是row
的ID
列表,它必须与你的data
顺序一致 -
strategy
: 排序策略。常用的有:-
verticalListSortingStrategy
:垂直列表 -
rectSortingStrategy
:网格布局
-
🧠 处理拖拽结束后的排序逻辑
拖动行的目的当然是为了重新排序,所以我们需要在 onDragEnd
中做个数据重排:
tsx
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event;
if (active && over && active.id !== over.id) {
setData((data) => {
const oldIndex = dataIds.indexOf(active.id);
const newIndex = dataIds.indexOf(over.id);
return arrayMove(data, oldIndex, newIndex);
});
}
}
🏗️ 整体结构 App
别忘了,SortableContext 需要包裹可拖动的元素区域,items 要传入对应的 id 列表,用于排序。
我们的表格组件最后就像这样:
tsx
<DndContext
collisionDetection={closestCenter}
modifiers={[restrictToVerticalAxis]}
onDragEnd={handleDragEnd}
sensors={sensors}
>
<table>
<thead>{/* 渲染头部 */}</thead>
<tbody>
<SortableContext items={dataIds} strategy={verticalListSortingStrategy}>
{table.getRowModel().rows.map((row) => (
<DraggableRow key={row.id} row={row} />
))}
</SortableContext>
</tbody>
</table>
</DndContext>

🔔 对官方示例代码可能存在一些删减的情况
代码地址🔗:Gitee
官方代码地址🔗: @tanStack/react-table