NocoBase SortHandle 组件详解:Table 拖拽排序手柄的用法与底层原理
【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase
SortHandle是 NocoBase@nocobase/client-v2包中Table组件拖拽排序功能的核心拖拽手柄(drag handle),它通过@dnd-kit/sortable提供的 activator 能力触发整行拖拽。本文基于 sort-handle.md 并结合 client-v2 源码,讲解 SortHandle 的用法、API、与Table/SortableRow的协作机制,以及自定制手柄列时的最佳实践。
SortHandle 是什么
SortHandle是Table拖拽排序的拖拽手柄。在 NocoBase 的设置页表格场景中,拖拽排序是行级操作:用户按住手柄(默认是一行MenuOutlined图标),将行拖到目标位置后松手,onSortEnd(from, to)回调被触发,由调用方持久化排序结果。
从 源码实现 看,SortHandle本身并不包含任何拖拽逻辑:
export const SortHandle: React.FC<{ id?: string | number; style?: React.CSSProperties }> = (props) => { const { id: _id, ...otherProps } = props; const dragSortContext = React.useContext(DragSortRowContext); return ( <span ref={dragSortContext?.setActivatorNodeRef} {...dragSortContext?.attributes} {...dragSortContext?.listeners} {...otherProps} className={classNames(sortHandleClass)} > <MenuOutlined /> </span> ); };关键点在于:它通过React.useContext(DragSortRowContext)读取父级行上下文,把setActivatorNodeRef、attributes、listeners挂到自身<span>上,从而成为行的拖拽激活点(activator)。因此,SortHandle必须放在Table的可拖拽行上下文里;单独渲染它不会触发拖拽。
基本用法
通常来说,直接给 Table 传isDraggable就够了,组件会自动在行首渲染一个手柄列;只有当你要自己控制手柄所在列时,才需要直接使用SortHandle。
import { SortHandle, Table } from '@nocobase/client-v2'; <Table rowKey="id" isDraggable showSortHandle={false} columns={[ { key: 'sort', width: 40, render: () => <SortHandle /> }, ...columns, ]} />;这个示例的要点:
isDraggable开启拖拽排序能力,Table内部会自动把components.body.row替换为SortableRow,并用DndProvider+SortableContext包裹表格主体;showSortHandle={false}关闭Table默认自动插入的拖拽手柄列(源码见 Table.tsx 中的showStandaloneHandleColumn判断);- 你自己定义一个宽度为
40的列,在render中渲染<SortHandle />,手柄就出现在你指定的这一列里。
SortHandle需要放在Table的可拖拽行上下文里。单独渲染它不会触发拖拽。
工作原理:DragSortRowContext 上下文链
SortHandle之所以能"躺着"触发拖拽,是因为它消费了由SortableRow提供的 DragSortRowContext:
type DragSortRowContextValue = { attributes?: DraggableAttributes; listeners?: SyntheticListenerMap; setActivatorNodeRef?: (node: HTMLElement | null) => void; }; export const DragSortRowContext = React.createContext<DragSortRowContextValue | null>(null);而SortableRow是 antd Tablecomponents.body.row的拖拽实现(对应文档 sortable-row.md),它使用useSortable({ id })建立行级拖拽能力,并读取 antd 注入到<tr>上的data-row-key作为排序 id:
const id = props['data-row-key']?.toString(); const { setNodeRef, setActivatorNodeRef, attributes, listeners, active, over } = useSortable({ id });随后它用DragSortRowContext.Provider把listeners、attributes、setActivatorNodeRef提供给行内的所有后代组件:
<DragSortRowContext.Provider value={{ listeners, attributes, setActivatorNodeRef }}> <tr ref={(node) => { setNodeRef(node); }} {...others} className={...} /> </DragSortRowContext.Provider>这样SortHandle就可以放在行的任意单元格中(通常是专门的第一列),而不必非得是整个行都可拖拽。整条链路是:
Table开启isDraggable后,tableComponents将components.body.row替换为SortableRow(Table.tsx);Table用DndProvider+SortableContext items={itemKeys}包裹表格,itemKeys由rowKey从dataSource逐行读取生成(Table.tsx);- 行渲染时
SortableRow通过useSortable注册自己,并通过 Context 把拖拽能力下发给SortHandle; - 用户按住
SortHandle拖动,Table的handleDragEnd解析from/to记录并调用onSortEnd(from, to)(Table.tsx)。
组件还通过DndProvider(来自@nocobase/flow-engine)暴露的onDragStart在拖拽开始时用snapshotSourceRow快照源<tr>,配合DragOverlay渲染行拖拽预览(Table.tsx),拖拽过程中行高亮使用主题色token.colorPrimary生成上下边框指示落点(SortableRow.tsx)。
API
SortHandle的对外参数如下:
| 参数 | 类型 | 说明 |
|---|---|---|
id | string \| number | 保留参数,当前不会参与渲染 |
style | React.CSSProperties | 自定义样式 |
其中id在源码中被显式解构为_id后丢弃(const { id: _id, ...otherProps } = props),即它是为未来扩展预留的参数,目前不影响任何行为。style等其余 props 会通过...otherProps透传到内部<span>上,可用于自定义手柄的大小、颜色、间距等。
与其他组件的协作
与Table的协作
Table在isDraggable开启时按以下规则决定手柄渲染位置(Table.tsx):
- 有
rowSelection且showSortHandle为真:手柄渲染在选择列单元格内(showHandleInSelection); - 无
rowSelection且showSortHandle为真:自动在行首插入一个宽度为sortHandleColumnWidth(默认40)的独立手柄列(showStandaloneHandleColumn),列的render直接返回<SortHandle />(Table.tsx); showSortHandle={false}:不渲染任何默认手柄,完全由你自定义。
也就是说,文档示例中showSortHandle={false}加自定义列的方式,本质上是把"手柄放哪一列"的控制权从Table手里拿回来。
与SortableRow的协作
SortHandle和SortableRow定义在同一个文件 SortableRow.tsx 中,并通过packages/core/client-v2/src/components/form/table/dnd/index.ts统一导出。在 flow 引擎的表格模型里,这两个组件也被复用:dragSortComponents.tsx直接 re-exportDragSortRowContext、SortHandle、SortableRow,而 dragSortHooks.tsx 中的useDragSortBodyWrapper会用DndProvider+SortableContext包裹<tbody>,并在拖拽结束时调用resource.runAction('move', { sourceId, targetId, sortField })完成服务端排序持久化,随后resource.refresh()刷新列表。
相关链接
- Table — 设置页表格,
isDraggable开启拖拽排序 - SortableRow — antd Table body row 的拖拽实现,为
SortHandle提供拖拽上下文
【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考