Refine v5 批量删除实战:使用 useDeleteMany 在 Ant Design 表格中实现多选删除
2026/9/12 12:01:51 网站建设 项目流程

Refine v5 批量删除实战:使用 useDeleteMany 在 Ant Design 表格中实现多选删除

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

useDeleteMany是 Refine 数据层(data hooks)中用于一次性删除多条记录的核心 Hook,本文以仓库中的table-antd-use-delete-many官方示例为主体,完整讲解如何在 Ant Design 表格上实现"勾选多行 → 点击删除"的批量删除交互,并深入packages/core源码剖析其底层实现:从 dataProvider 的deleteMany调用、deleteOne降级逻辑,到乐观更新、查询失效(invalidate)与实时通知机制。读完本文,你将掌握useDeleteMany的全部参数、返回值与最佳实践,并理解它在 Refine 数据流中扮演的角色。

示例场景:表格多选批量删除

原文档 useDeleteMany.md 描述的场景非常明确:useDeleteMany一次从数据库中删除多条数据,而在示例中,我们用它从表格里移除多条记录——用户通过勾选行选中记录,点击删除按钮即可批量移除。

对应示例源码位于 examples/table-antd-use-delete-many,应用使用@refinedev/simple-rest连接https://api.fake-rest.refine.dev这一演示 API(见 App.tsx),资源定义为posts,包含 list / create / edit / show 四条路由。核心页面是 src/pages/posts/list.tsx。

从零实现:勾选、删除、刷新

1. 初始化表格与分类数据

页面通过useTable<IPost>()获取 Ant Design 表格所需的tableProps,同时用useMany拉取文章关联的categories,用于把category.id渲染成分类标题:

const { tableProps } = useTable<IPost>(); const categoryIds = tableProps?.dataSource?.map((item) => item.category.id) ?? []; const { result: data, query: { isLoading }, } = useMany<ICategory>({ resource: "categories", ids: categoryIds, queryOptions: { enabled: categoryIds.length > 0, }, });

注意这里的queryOptions.enabled:当表格还没有数据时categoryIds为空数组,此时禁用该查询,避免发无意义的请求。

2. 调用 useDeleteMany 并执行删除

这是整页的核心:

const { mutate, mutation: { isPending: deleteManyIsLoading }, } = useDeleteMany<IPost>(); const deleteSelectedItems = () => { mutate( { resource: "posts", ids: selectedRowKeys.map(String), }, { onSuccess: () => { setSelectedRowKeys([]); }, }, ); };

要点解析:

  • mutate是触发删除的函数,必填参数resource(资源名,对应 API 端点路径)与ids(要删除的记录主键数组)。
  • selectedRowKeys来自 Ant Design 的rowSelection状态,map(String)确保主键统一为字符串形式传给 Hook。
  • mutation.isPending中解构出的deleteManyIsLoading用来驱动按钮的loading态。
  • onSuccess回调在删除成功后清空选中状态——这正是原文档强调的:useDeleteManymutationOptions不支持onSuccess/onError(它们会被内置实现覆盖),需要这类回调时请像这里一样作为mutate的第二个参数传入。

3. 接入表格多选

Ant Design 的Table通过rowSelection开启行勾选:

const [selectedRowKeys, setSelectedRowKeys] = React.useState<React.Key[]>([]); const onSelectChange = (selectedRowKeys: React.Key[]) => { setSelectedRowKeys(selectedRowKeys); }; const rowSelection = { selectedRowKeys, onChange: onSelectChange, selections: [ Table.SELECTION_ALL, Table.SELECTION_INVERT, Table.SELECTION_NONE, ], }; const hasSelected = selectedRowKeys.length > 0; return ( <List headerProps={{ subTitle: ( <> <Button type="primary" onClick={deleteSelectedItems} disabled={!hasSelected} loading={deleteManyIsLoading} > Delete Selected </Button> <span style={{ marginLeft: 8 }}> {hasSelected ? `Selected ${selectedRowKeys.length} items` : ""} </span> </> ), }} > <Table {...tableProps} rowSelection={rowSelection} rowKey="id"> {/* 列定义:ID / Title / Status / Category / Actions */} </Table> </List> );

这里有三处值得注意的细节:

  • Table.SELECTION_ALL / SELECTION_INVERT / SELECTION_NONE让表头下拉菜单支持"全选 / 反选 / 取消全选"。
  • disabled={!hasSelected}保证未选中任何行时按钮不可点,配合loading防止重复提交。
  • List组件的headerProps.subTitle把操作按钮放进页面标题栏,是一种整洁的布局做法。

完整的列定义还包含操作列(编辑、查看按钮),位于 list.tsx,可作为参照。

批量删除的关键参数

原文档将useDeleteMany定位为 TanStack QueryuseMutation的扩展版:它继承useMutation的全部能力,并额外增加了 Refine 的数据层能力(通知、失效、实时发布等)。参考 use-delete-many 文档,常用参数如下:

参数类型默认值说明
resource(必填)string传给 dataProviderdeleteMany的资源名,通常即 API 端点路径
ids(必填)BaseKey[]要删除的记录主键数组
mutationMode"pessimistic" \| "optimistic" \| "undoable""pessimistic"决定变更在何时执行(详见下文)
undoableTimeoutnumber5000(毫秒)undoable模式下等待执行的时长
onCancel(cancelMutation) => voidundoable模式下提供取消句柄;定义后系统不再自动弹撤销通知
successNotification/errorNotificationSuccessErrorNotification内置文案自定义成功 / 失败通知
metaMetaDataQuery{}透传给 dataProvider 的附加信息(如自定义请求头、GraphQL 查询)
dataProviderNamestring"default"存在多个 dataProvider 时指定使用哪一个
invalidates"all" \| "resourceAll" \| "list" \| "many" \| "detail" \| false["list", "many"]变更完成后要失效并重取的查询

三种 mutationMode 的取舍

  • pessimistic(默认):先请求后端,成功后再更新界面。最保守、数据最可靠。
  • optimistic:立即在界面上移除记录,请求失败时回滚缓存。
  • undoable:等待undoableTimeout毫秒(默认 5 秒)后才真正执行,期间可撤销,适合误删保护场景。

这三种模式在源码测试 packages/core/src/hooks/data/useDeleteMany.spec.tsx 中均有对应用例验证(详见下文"测试与验证")。

多 dataProvider 与自定义通知示例

const { mutate, mutation } = useDeleteMany(); mutate({ resource: "products", ids: [1, 2, 3], dataProviderName: "second-data-provider", successNotification: (data, ids, resource) => ({ message: `${ids.length} products deleted.`, description: "Success with no errors", type: "success", }), errorNotification: (error, ids, resource) => ({ message: "Something went wrong", description: error.message, type: "error", }), });

源码剖析:useDeleteMany 的完整调用链

useDeleteMany的实现位于 packages/core/src/hooks/data/useDeleteMany.ts,阅读它可以把"黑盒"变成"白盒"。

1. mutationFn:优先 deleteMany,降级 deleteOne

Hook 底层创建了一个 TanStack QueryuseMutation(useDeleteMany.ts),其mutationFn内部:

  • 通过useResourceParamsselect解析resource,用useDataProviderdataProviderName选出目标 provider;
  • 如果 provider 实现了deleteMany,直接调用selectedDataProvider.deleteMany({ resource, ids, meta, variables })
  • 如果 provider 没有deleteMany,则退化为handleMultiple(ids.map((id) => deleteOne({ ... })))——即逐条调用deleteOne,对每个 id 各发一次请求。原文档明确提示:这是不推荐的做法,最好在 dataProvider 中实现deleteMany

@refinedev/rest为例,其deleteMany会按getEndpoint解析端点、buildHeaders组装请求头,并对params.resource发起DELETE请求(见 create-data-provider.ts)。

2. onMutate:乐观更新与缓存快照

mutationMode !== "pessimistic"时,onMutate(useDeleteMany.ts)会:

  1. queryClient.cancelQueries取消进行中的相关查询;
  2. getQueriesData保存当前缓存快照(previousQueries),供失败回滚;
  3. listmanyone三类查询做本地更新——从列表数据中过滤掉被删除的 ids,并把total减一。

这也解释了为什么示例删除后表格自动刷新:数据已经通过缓存层同步更新。

3. onSettled / onSuccess / onError:失效、通知与回滚

  • onSettled(useDeleteMany.ts):默认调用invalidateStore({ invalidates: ["list", "many"] })失效列表类查询(可用invalidates参数覆盖),并清理 undoable 队列中的通知条目。
  • onSuccess:从缓存移除被删记录的one查询;发出默认成功通知(文案由notifications.deleteSuccess翻译键控制);若有 Live Provider,则调用publishresources/{resource}频道发布deleted事件(useDeleteMany.ts);同时通过useLog记录deleteMany审计日志。
  • onError:用快照previousQueries恢复被乐观更新的缓存,触发useOnError的全局错误处理,并弹出默认错误通知(除非错误为主动取消的mutationCancelled)。

4. undoable 模式实现

mutationMode === "undoable"时,mutationFn 不会立刻请求后端,而是构造一个 Promise,通过notificationDispatch向 undoable 队列注册cancelMutationdoMutation,等待undoableTimeout秒(useDeleteMany.ts);倒计时结束才真正执行删除,期间用户可撤销。

测试与验证:三种模式与 E2E 行为

仓库同时提供了单元测试与端到端测试,可作为实现行为的权威佐证。

单元测试:pessimistic / optimistic / undoable

packages/core/src/hooks/data/useDeleteMany.spec.tsx 使用MockJSONServerTestWrapper渲染 Hook:

  • pessimisticmutate({ resource: "posts", ids: ["1"] })后等待mutation.isSuccess为真;
  • optimistic:mock 一个 1000ms 后 reject 的deleteMany,先断言列表长度由 2 变为 0(乐观移除),请求失败后断言长度恢复为 2(回滚生效);
  • undoablemutationMode: "undoable", undoableTimeout: 1000下执行删除并最终成功。

E2E 测试:完整用户路径

cypress/e2e/table-antd-use-delete-many/all.cy.ts 用 Cypress 验证了真实页面行为:

  1. 点击表头复选框全选后,.ant-table-row-selected应恰好有 10 行;
  2. 未选中任何行时,主按钮(.ant-btn-primary)应处于 disabled 状态;
  3. 勾选两行后点击 "Delete" 按钮,应发出两次删除请求(cy.wait("@deletePost")两次)。

运行示例

你可以通过 Refine CLI 在本地快速启动该示例:

npm create refine-app@latest -- --example table-antd-use-delete-many

示例完整源码位于 examples/table-antd-use-delete-many,依赖@refinedev/core@refinedev/antd@refinedev/simple-restuseDeleteMany的类型定义与实现可进一步查阅 packages/core/src/hooks/data/useDeleteMany.ts 及其配套文档 documentation/docs/data/hooks/use-delete-many/index.md。

小结

本文以官方table-antd-use-delete-many示例为主线,完整覆盖了"表格多选 + 批量删除"的落地路径:先用useTable+rowSelection收集选中主键,再交给useDeleteManymutate执行;随后从源码层厘清了 Hook 的完整生命周期——deleteMany优先、deleteOne降级、乐观更新、失败回滚、查询失效、通知与实时发布。掌握了这些,你便可以在自己的 Refine 应用中安全、高效地实现批量删除,并依据mutationMode在数据可靠性、交互即时性与误删保护之间做出合理权衡。

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询