Refine Mantine ExportButton 组件详解:数据导出按钮的定制与 useExport 实战
2026/9/13 7:32:17 网站建设 项目流程

Refine Mantine ExportButton 组件详解:数据导出按钮的定制与 useExport 实战

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

<ExportButton>是 Refine 在 Mantine 适配层中提供的开箱即用导出按钮:它本质上是 Mantine<Button>为主体,结合packages/mantinepackages/core中的源码与测试,讲解该组件的用法、全部属性、底层实现原理,以及如何与useExport组合成完整的导出实战方案。

:::simple 本文速览

  • 掌握ExportButton的基本用法、hideText等核心属性;
  • 通过源码理解按钮的“纯展示”定位与默认文案/图标的来源;
  • 结合useExport的完整参数表,实现分页拉取、字段映射、自定义文件名与 CSV 下载;
  • 了解 Swizzle 定制入口与跨 UI 库共用的测试基座。

:::

ExportButton 是什么

<ExportButton>是一个 Mantine<Button>,带有默认的导出图标和默认文本 “Export”。它在数据层面只有展示价值(presentational),真正的导出逻辑由核心包提供的useExportHook 承担,两者通过onClickloading串联起来。

这一“纯展示”定位在源码中有明确体现。Mantine 版实现 中,组件只做了三件事:

  1. 通过useExportButton()获取本地化后的默认文案label
  2. 根据hideText决定渲染ActionIcon(仅图标)还是Button(图标 + 文本);
  3. 统一挂载data-testidclassName,供测试与样式覆盖使用。
import { ActionIcon, Button } from "@mantine/core"; import { IconFileExport } from "@tabler/icons-react"; export const ExportButton: React.FC<ExportButtonProps> = ({ hideText = false, children, loading = false, svgIconProps, ...rest }) => { const { label } = useExportButton(); // ... return hideText ? ( <ActionIcon loading={loading} aria-label={label} ...> <IconFileExport size={18} {...svgIconProps} /> </ActionIcon> ) : ( <Button variant="default" loading={loading} leftIcon={<IconFileExport size={18} />} ...> {children ?? label} </Button> ); };

默认文案label由核心包的按钮 Hook 体系提供。packages/core/src/hooks/button/index.tsx 中,useExportButtonuseSaveButtonuseImportButton一样,统一收敛到useActionableButton({ type: "export" }),由 i18n 上下文给出 “Export” 之类的翻译,因此当你的应用配置了多语言时,按钮文字会自动随语言包切换。

基本用法:给列表页加上导出能力

导出按钮最常见的应用场景是列表页(List)头部。它本身不包含任何导出逻辑,需要配合@refinedev/coreuseExportHook 使用:

import { useExport } from "@refinedev/core"; import { List, ExportButton } from "@refinedev/mantine"; import { Table, Pagination } from "@mantine/core"; import { useTable } from "@refinedev/react-table"; import { ColumnDef, flexRender } from "@tanstack/react-table"; const PostList: React.FC = () => { const columns = React.useMemo<ColumnDef<IPost>[]>( () => [ { id: "id", header: "ID", accessorKey: "id" }, { id: "title", header: "Title", accessorKey: "title" }, ], [], ); const { reactTable: { getHeaderGroups, getRowModel }, refineCore: { setCurrentPage, pageCount, currentPage }, } = useTable({ columns }); const { triggerExport, isLoading: exportLoading } = useExport<IPost>({ mapData: (item) => ({ id: item.id, post_title: item.title, }), pageSize: 10, maxItemCount: 50, }); return ( <List headerButtons={ <ExportButton loading={exportLoading} onClick={triggerExport} /> } > <Table> <thead> {getHeaderGroups().map((headerGroup) => ( <tr key={headerGroup.id}> {headerGroup.headers.map((header) => ( <th key={header.id}> {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} </th> ))} </tr> ))} </thead> <tbody> {getRowModel().rows.map((row) => ( <tr key={row.id}> {row.getVisibleCells().map((cell) => ( <td key={cell.id}> {flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ))} </tr> ))} </tbody> </Table> <br /> <Pagination position="right" total={pageCount} page={currentPage} onChange={setCurrentPage} /> </List> ); }; interface IPost { id: number; title: string; }

要点说明:

  • 触发链路:点击按钮 → 调用triggerExportuseExport内部通过 data provider 的getList分批拉取数据 → 用 papaparse 序列化为 CSV → 触发浏览器下载;
  • 加载态loading={exportLoading}让按钮在导出过程中显示 loading 状态并防止重复点击;
  • 位置:通过List组件的headerButtons插槽放入列表页头部工具栏,这是 Refine 推荐的放法,也可以放到任意你想放置的位置。

Properties:ExportButton 的全部属性

hideText

hideText用于控制是否显示按钮文字。当为true时,只显示导出图标:

import { ExportButton } from "@refinedev/mantine"; const MyExportComponent = () => { return <ExportButton hideText />; };

从源码可见,hideText模式下组件渲染的是 Mantine 的ActionIcon(图标按钮),并自动带上aria-label={label},保证无障碍可访问性;非隐藏模式下渲染Button,文字优先使用children,未传children时才回退到默认的label

其余常用属性

ExportButton的属性类型定义为RefineExportButtonProps<ButtonProps, CommonButtonProps>(见 packages/mantine/src/components/buttons/types.ts),即除下面列出的自定义属性外,MantineButton的全部属性(variantsizecolordisabledonClick等)均可直接透传:

属性类型默认值说明
hideTextbooleanfalsetrue时仅显示图标,隐藏 “Export” 文本
childrenReactNode自定义按钮文字,优先级高于默认文案
loadingbooleanfalse导出进行中显示加载态(源码中透传给Button/ActionIcon
svgIconPropsOmit<IconProps, "ref">自定义IconFileExport图标的尺寸、颜色等属性
hiddenbooleanCommonButtonProps提供的通用隐藏开关
onClick事件回调点击处理,通常绑定triggerExport

此外,Mantine 版按钮内部使用mapButtonVariantToActionIconVariantButton的 variant 映射为ActionIcon的 variant,因此在hideText与普通模式之间切换时,视觉风格可以保持一致。

底层原理:useExport 如何工作

虽然本文主角是ExportButton,但要写出可用的导出功能,理解 useExport 实现 是必要的。核心流程如下:

  1. 解析资源:通过useResourceParams拿到当前资源名;若存在多个 data provider,用pickDataProvider选择正确的 provider。
  2. 分批拉取:以pageSize(默认 20)为一批循环调用getListpagination.mode固定为"server"以启用服务端分页,直到满足以下任一条件停止:
    • 已拉取数据量达到maxItemCount
    • rawData.length === total(所有数据已取完)。
  3. 字段映射:对每条记录执行mapData,用于重命名/裁剪导出字段(例如把item.title输出为post_title)。
  4. 序列化与下载:使用 papaparse 的unparse生成 CSV(默认quotes: trueheader: true),并通过downloadInBrowser触发浏览器下载;文件名中的空格会被替换为下划线,并支持 BOM 前缀(默认开启,便于 Excel 正确识别 UTF-8)。

useExport的完整可配置项如下:

参数类型默认值说明
resourcestring从路由读取要导出的资源名
mapDataMapDataFn(item) => item对每条记录执行的字段映射函数
sortersCrudSort[]导出数据的排序规则
filtersCrudFilter[]导出数据的过滤规则
maxItemCountnumber导出的最大记录数上限
pageSizenumber20每批拉取的条数(即getList的页大小)
unparseConfigUnparseConfig{ quotes: true, header: true }papaparse 序列化配置
metaMetaQuery传给 data provider 的元数据
dataProviderNamestring多 data provider 时指定使用的 provider
onError(error) => void导出失败回调
downloadbooleantrue是否在浏览器触发下载
filenamestring资源名-当前时间自定义导出文件名(不含扩展名)
useTextFilebooleanfalsetrue时导出.txt文本文件
useBombooleantrue是否在文件头写入 BOM
titlestring"My Generated Report"导出文件的标题文本
showTitlebooleanfalse是否在文件首行输出标题

返回值为{ isLoading, triggerExport }isLoading驱动按钮的 loading 态,triggerExport是异步函数,调用后返回生成的 CSV 字符串(或undefined)。

一个更贴近实战的自定义示例:

const { triggerExport, isLoading } = useExport<IPost>({ resource: "posts", filename: "posts_report", pageSize: 50, useTextFile: false, title: "Posts Report", showTitle: true, mapData: (item) => ({ id: item.id, title: item.title }), onError: (error) => console.error("Export failed:", error), }); return <ExportButton loading={isLoading} onClick={triggerExport} />;

Swizzle:一键定制 ExportButton

如果你需要深度定制(例如更换图标、调整默认样式、增加业务逻辑),Refine 官方推荐使用 Swizzle 机制:运行 Refine CLI 的 swizzle 命令后,组件源码会被复制进你的项目src目录,此后你可以直接修改这份副本而不影响框架升级。

npm run refine swizzle @refinedev/mantine ExportButton

Swizzle 能力对应的文档元数据为swizzle: true(见 文档 frontmatter)。定制后的按钮依然可以依赖useExportButton获取本地化文案,从而在保持团队 i18n 一致性的前提下自由改版。

测试基座:跨 UI 库共用的导出按钮行为

Refine 的 UI 组件测试是跨适配层共用的。Mantine 版测试 直接委托给@refinedev/ui-tests中的buttonExportTests,它验证了四条核心行为(见 packages/ui-tests/src/tests/buttons/export.tsx):

  1. 无 props 时默认渲染出 “Export” 文本;
  2. 挂载了正确的data-testidRefineButtonTestIds.ExportButton);
  3. 传入children时优先渲染自定义文本;
  4. 设置hideText后不再出现 “Export” 文本。

这意味着你在 Ant Design、Material UI、Chakra UI 等任意适配层中使用对应导出按钮时,这些基础行为都是被同一套测试保障的,可以放心迁移。

小结

  • <ExportButton>是纯展示组件:图标 + “Export” 文案,默认文案来自 i18n,支持children覆盖与hideText图标模式;
  • 导出逻辑全部交给useExport:它负责分页拉取、字段映射、papaparse 序列化与浏览器下载,并返回isLoading/triggerExport与按钮对接;
  • 合理使用参数pageSize控制批大小、maxItemCount防止超大导出、mapData裁剪字段、filename/useTextFile/useBom定制输出文件;
  • 需要深度定制时:优先使用 Refine CLI 的 Swizzle 将组件复制到项目内修改,保持升级兼容的同时实现自定义样式与逻辑。

掌握了这套“按钮 + Hook”的组合,你就能在 Refine Mantine 应用中快速为任意资源列表添加一键导出的完整能力。

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

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

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

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

立即咨询