Refine v5 中 Ant Design DeleteButton 的完整指南:确认弹窗、useDelete 调用链与权限控制
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
<DeleteButton>是 Refine 基于 Ant Design 二次封装的危险操作按钮组件,专门用于删除场景。它集成了 Ant Design 的<Button>与<Popconfirm>,点击后会弹出确认框,确认后才通过 dataProvider 的deleteOne方法真正执行删除——这种"先确认、后执行"的设计能有效防止误删。本文围绕该组件的核心脉络展开:先看它在表格中的典型用法,再逐一拆解recordItemId、resource、onSuccess、mutationMode、hideText、accessControl等关键属性,最后深入源码与测试,讲清确认弹窗文案、useDeleteButton内部调用链以及 i18n 机制,让你在管理后台中安全、优雅地实现删除功能。
组件概览:基于<Button>与<Popconfirm>的组合
根据官方文档(documentation/docs/ui-integrations/ant-design/components/buttons/delete-button/index.md),<DeleteButton>底层由 Ant Design 的<Button>与<Popconfirm>两个组件组合而成:
- 当你尝试删除某条数据时,页面上会弹出确认气泡(Popconfirm),询问是否确认删除;
- 点击确认后,组件会执行由 dataProvider 提供的
useDelete方法,真正发起删除请求。
这一行为在源码中得到了完全印证。打开 packages/antd/src/components/buttons/delete/index.tsx,可以看到组件的渲染结构:
return ( <Popconfirm key="delete" okText={confirmOkText ?? defaultConfirmOkLabel} cancelText={confirmCancelText ?? defaultCancelLabel} okType="danger" title={confirmTitle ?? defaultConfirmTitle} okButtonProps={{ disabled: loading }} onConfirm={onConfirm} disabled={isDisabled} > <Button danger loading={loading} icon={<DeleteOutlined />} title={title} disabled={isDisabled} >import { List, useTable, DeleteButton, } from "@refinedev/antd"; import { Table } from "antd"; const PostList = () => { const { tableProps } = useTable<IPost>(); return ( <List> <Table {...tableProps} rowKey="id"> <Table.Column dataIndex="id" title="ID" /> <Table.Column dataIndex="title" title="Title" width="50%" /> <Table.Column<IPost> title="Actions" dataIndex="actions" key="actions" render={(_, record) => ( <DeleteButton size="small" recordItemId={record.id} /> )} width="50%" /> </Table> </List> ); }; interface IPost { id: number; title: string; }关键点在于recordItemId={record.id}——它把当前行的记录 ID 绑定到删除按钮上。点击按钮后,组件会自动推断资源名与记录 ID,触发useDelete,从而删除对应记录。文档中的 live 示例还展示了该页面挂载在/posts路由下,资源名定义为posts。
核心属性详解
recordItemId:指定要删除哪条记录
recordItemId用于管理将要删除的记录。默认情况下,它会从路由参数中自动推断(例如在posts/edit/123页面中,123会自动成为删除目标),因此在编辑/详情页内放置删除按钮时通常可以省略。
在列表页这类无法从路由推断 ID 的场景,则需要显式传入:
import { DeleteButton } from "@refinedev/antd"; const MyDeleteComponent = () => { return ( <DeleteButton resource="posts" recordItemId="123" /> ); };点击按钮后,useDelete会删除资源为posts、ID 为123的记录。注意这里recordItemId传入的是字符串"123",而BaseKey类型同样支持数字等类型,具体以你的主键类型为准。
resource:指定删除哪个资源的记录
resource用于管理将要删除的是哪个资源的记录,默认同样从路由参数推断。当需要删除其他资源的数据(例如在 posts 页面里删除 categories 数据)时,显式传入资源名:
import { DeleteButton } from "@refinedev/antd"; const MyDeleteComponent = () => { return <DeleteButton resource="categories" recordItemId="123" />; };点击后,useDelete会删除资源为categories、ID 为123的记录。
同名资源的处理(identifier):如果你在<Refine/>中定义了多个同名的资源,可以传入identifier而非name来区分。identifier只作为资源的主匹配键,dataProvider 的方法仍然使用<Refine/>组件中定义的资源name来执行。更详细的说明可参考 documentation/docs/core/refine-component/index.md 中的identifier小节。
onSuccess:删除成功后的回调
onSuccess允许你在删除请求返回结果后做额外处理,例如打日志、刷新统计、跳转等。文档示例中在删除成功后console.log返回值:
import { DeleteButton } from "@refinedev/antd"; const MyDeleteComponent = () => { return ( <DeleteButton resource="posts" recordItemId="1" onSuccess={(value) => { console.log(value); }} /> ); };文档配套的 live 示例还演示了一个自定义 dataProvider:通过覆盖deleteOne方法模拟 500ms 延迟后返回{ message: "You have successfully deleted the record" },此时onSuccess收到的value就是这个自定义返回对象。这说明onSuccess拿到的是 dataProviderdeleteOne的返回值(即DeleteOneResponse),因此你可以利用它拿到服务端返回的业务信息,而不只是"请求成功"这个状态。
mutationMode:删除的三种提交模式
mutationMode决定删除操作的提交方式,可选值为:
pessimistic(默认):先调用 dataProvider,成功后再更新 UI,最安全;optimistic:先更新 UI,再后台调用 dataProvider,响应更快但失败时需回滚;undoable:先更新 UI,同时在指定时间内提供撤销入口,超时后才真正调用 dataProvider。
在表格中为单个按钮设置示例:
import { List, DeleteButton, useTable } from "@refinedev/antd"; import { Table } from "antd"; const PostList = () => { const { tableProps } = useTable<IPost>(); return ( <List> <Table {...tableProps} rowKey="id"> <Table.Column dataIndex="id" title="ID" /> <Table.Column dataIndex="title" title="Title" /> <Table.Column<IPost> title="Actions" dataIndex="actions" render={(_, record) => ( <DeleteButton size="small" recordItemId={record.id} mutationMode="undoable" /> )} /> </Table> </List> ); };对删除这类危险操作,推荐优先使用默认的pessimistic;undoable适合希望给用户反悔机会的场景(删除后出现"撤销"提示)。更完整的模式对比与配置说明见 documentation/docs/advanced-tutorials/mutation-mode.md。
hideText:只显示图标
hideText用于控制是否显示按钮文字。设为true时,按钮只保留图标(DeleteOutlined),适合操作列空间紧张的场景:
import { DeleteButton } from "@refinedev/antd"; const MyDeleteComponent = () => { return ( <DeleteButton hideText={true} recordItemId="123" /> ); };从源码可以看到,hideText的默认值为false;且当传入children时,会优先渲染children作为按钮内容(children ?? label)。
accessControl:接入权限控制
accessControl属性用于控制删除按钮的权限表现,仅在向<Refine/>提供了accessControlProvider时生效。它支持两个子属性:
enabled:是否启用访问控制检查(默认组件本身会启用,可显式控制);hideIfUnauthorized:用户无权限时是否直接隐藏按钮。
import { DeleteButton } from "@refinedev/antd"; export const MyListComponent = () => { return ( <DeleteButton accessControl={{ enabled: true, hideIfUnauthorized: true, }} /> ); };若不设置hideIfUnauthorized,无权限时按钮通常表现为禁用而不是隐藏。完整的accessControlProvider定义可参考 documentation/docs/authorization/access-control-provider/index.md。
API Reference 与外部属性
该组件的属性完整定义见文档中的Properties表格(@refinedev/antd/DeleteButton)。此外,它接受 Ant Design<Button>的全部 props(如size、danger、icon、loading等),这意味着你可以完全按 Ant Design 的按钮规范自由定制外观与行为。
源码探秘:useDeleteButton 的调用链
<DeleteButton>本身是纯展示层,真正的逻辑集中在 Refine Core 提供的useDeleteButton钩子中。它的实现位于 packages/core/src/hooks/button/delete-button/index.tsx,核心流程如下:
export function useDeleteButton(props: DeleteButtonProps): DeleteButtonValues { const translate = useTranslate(); const { mutation: { mutate, isPending, variables }, } = useDelete(); const { setWarnWhen } = useWarnAboutChange(); const { mutationMode } = useMutationMode(props.mutationMode); const { id, resource, identifier } = useResourceParams({ resource: props.resource, id: props.id, }); const { title, disabled, hidden, canAccess } = useButtonCanAccess({ action: "delete", accessControl: props.accessControl, meta: props.meta, id, resource, }); const label = translate("buttons.delete", "Delete"); const confirmOkLabel = translate("buttons.delete", "Delete"); const confirmTitle = translate("buttons.confirm", "Are you sure?"); const cancelLabel = translate("buttons.cancel", "Cancel"); const loading = id === variables?.id && isPending; const onConfirm = () => { if (id && identifier) { setWarnWhen(false); mutate( { id, resource: identifier, mutationMode, successNotification: props.successNotification, errorNotification: props.errorNotification, meta: props.meta, dataProviderName: props.dataProviderName, invalidates: props.invalidates, }, { onSuccess: props.onSuccess, }, ); } }; return { label, title, hidden, disabled, canAccess, loading, confirmOkLabel, cancelLabel, confirmTitle, onConfirm, }; }从源码可以提炼出以下关键事实:
- 参数兜底:
useResourceParams会解析resource与id,当组件未显式传入时,从当前路由上下文自动推断(这正是文档所说"默认从路由参数推断"的底层实现)。useDeleteButton同时返回resource与identifier,后者用于在mutate时作为resource参数传递。 - 确认框文案走 i18n:按钮文字(
buttons.delete)、确认标题(buttons.confirm→ "Are you sure?")、确认/取消按钮文字(buttons.delete/buttons.cancel)全部通过useTranslate读取;未命中时使用内置英文默认值。 - 删除走
useDelete:确认后调用useDelete的mutate,并携带mutationMode、successNotification、errorNotification、meta、dataProviderName、invalidates等参数,onSuccess作为 mutation 回调传入。useDelete的完整说明见 documentation/docs/data/hooks/use-delete/index.md,其底层通过 dataProvider 的deleteOne方法发起请求(dataProvider 接口定义见 documentation/docs/guides-concepts/data-fetching/data-provider-interface.md)。 - loading 状态:
loading = id === variables?.id && isPending——只有当当前按钮的id恰好等于正在执行中的删除请求的id时,该按钮才进入 loading 状态,避免列表中其他行的按钮被"连带转圈"。 - 权限与表单联动:
useButtonCanAccess负责访问控制检查,setWarnWhen(false)则在删除前重置"未保存更改"警告(useWarnAboutChange),防止删除操作被表单未保存提示拦截。
确认框文本的 i18n 测试佐证
useDeleteButton的测试位于 packages/core/src/hooks/button/delete-button/index.spec.tsx,其中两个用例直接验证了文案机制:
- 默认(无 i18nProvider)时返回
label: "Delete"、confirmOkLabel: "Delete"、confirmTitle: "Are you sure?"、cancelLabel: "Cancel"; - 传入自定义
i18nProvider后,buttons.delete、buttons.confirm、buttons.cancel三个 key 会被翻译成自定义文案(如 "Delete (i18n)")。
也就是说,只需在 i18n 资源中覆盖buttons.delete/buttons.confirm/buttons.cancel,即可全局本地化删除按钮与确认框的文字。
组件的单元测试入口
Ant Design 侧对<DeleteButton>的测试通过复用@refinedev/ui-tests的通用按钮测试套件完成(见 packages/antd/src/components/buttons/delete/index.spec.tsx):
import { buttonDeleteTests } from "@refinedev/ui-tests"; import { DeleteButton } from "./"; describe("Delete Button", () => { buttonDeleteTests.bind(this)(DeleteButton); });这意味着删除按钮的渲染、确认交互、禁用/隐藏、权限等行为都由统一的测试契约保障,也侧面印证了各 UI 框架(Ant Design、MUI、Mantine、Chakra UI)的删除按钮行为一致。
配套能力:批量删除与相关参考
批量删除场景
当需要支持表格多选批量删除时,文档建议使用useDeleteMany而非逐行<DeleteButton>。仓库示例 examples/table-antd-use-delete-many/src/pages/posts/list.tsx 展示了完整做法:通过rowSelection收集选中的行,点击 "Delete Selected" 按钮后调用useDeleteMany一次性删除多个 ID,成功后清空选中态:
const { mutate, mutation: { isPending: deleteManyIsLoading }, } = useDeleteMany<IPost>(); const deleteSelectedItems = () => { mutate( { resource: "posts", ids: selectedRowKeys.map(String), }, { onSuccess: () => { setSelectedRowKeys([]); }, }, ); };该示例同时展示了把EditButton、ShowButton等与删除按钮组合放进操作列(搭配hideText)的常见布局。
快速自定义:使用 Refine CLI 进行 Swizzle
文档特别提示:你可以通过Refine CLI对这个组件进行 swizzle(把组件源码"弹出"到你的项目中)以便深度自定义。Refine CLI 的安装与使用方法见 documentation/docs/packages/cli/index.md。swizzle 之后,你可以直接修改弹出副本的 JSX 结构、样式或默认行为,同时保留原有 props 接口。
小结
<DeleteButton>把"危险操作需二次确认"这一最佳实践固化成了开箱即用的组件:外层由 Ant Design 的Popconfirm兜底确认交互,内层逻辑由useDeleteButton串联useDelete→ dataProviderdeleteOne的完整调用链,并天然支持路由参数推断、i18n 文案、访问控制和三种 mutation 模式。日常使用中只需记住几个要点:列表页记得传recordItemId,跨资源操作记得传resource,需要权限控制时配置accessControlProvider并结合hideIfUnauthorized,追求极简布局时用hideText。若需要批量删除,则切换到useDeleteMany;若要深度定制组件本身,可用 Refine CLI 进行 swizzle。
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考