Refine v5 的 useImport Hook 完全指南:从 CSV 批量导入到数据 Provider 的底层实现
2026/9/12 11:15:22 网站建设 项目流程

Refine v5 的 useImport Hook 完全指南:从 CSV 批量导入到数据 Provider 的底层实现

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

useImport是 Refine 的 Ant Design 集成(@refinedev/antd)中用于从 CSV 文件批量导入数据到管理后台的核心 Hook。它逐行(或按批次)调用数据提供者的create/createMany方法,内部借助 Papa Parse 完成 CSV 解析,并返回与 Ant Design<Upload><Button>组件直接兼容的属性。阅读本文后,你将掌握 useImport 的全部配置项、返回值、关系数据映射(mapData)实战技巧,以及从@refinedev/core@refinedev/antd的完整调用链与底层实现原理。

本文以 documentation/docs/ui-integrations/ant-design/hooks/use-import/index.md 为骨架,并结合 packages/antd/src/hooks/import/index.tsx、packages/core/src/hooks/import/index.tsx 等仓库源码展开。

useImport 是什么

useImportHook 允许你从一个CSV文件导入数据。对于文件中的每一行,它会根据你的配置调用数据提供者的createcreateMany方法。内部使用 Papa Parse 解析文件内容,返回值与 Ant Design 的<Upload><Button>组件完全兼容。

从包结构看,@refinedev/antduseImport是对@refinedev/core中同名 Hook 的扩展封装(antd 封装位于 packages/antd/src/hooks/import/index.tsx,core 实现位于 packages/core/src/hooks/import/index.tsx)。这意味着 core 版本的所有能力你都可以使用,antd 版本在其之上额外提供了:

  • 与 Ant Design 组件体系对接的uploadProps/buttonProps
  • 默认的导入进度通知(基于 Ant Design 的notificationProgress组件,见 packages/antd/src/hooks/import/index.tsx)。

也就是说,@refinedev/antd版本面向的是"接入 Ant Design 界面"的场景;如果你在无 UI 依赖(headless)的环境下工作,可以直接使用 core 版本。

基础用法

配合 ImportButton 使用(推荐)

最简单的用法是配合@refinedev/antd提供的<ImportButton>组件:

import { ImportButton, useImport } from "@refinedev/antd"; export const PostList: React.FC = () => { const importProps = useImport(); return <ImportButton {...importProps}>Import</ImportButton>; };

<ImportButton>内部用一个<Upload>包裹<Button>,并内置了导入图标(ImportOutlined)、data-testid与 className(见 packages/antd/src/components/buttons/import/index.tsx)。它的属性类型ImportButtonProps要求分别传入uploadPropsbuttonProps(见 packages/antd/src/components/buttons/types.ts),这恰好与useImport的返回值一一对应,因此可以直接展开传递。

更多关于<ImportButton>的细节,可参考 documentation/docs/ui-integrations/ant-design/components/buttons/import-button/index.md。

不使用 ImportButton 的自定义用法

如果你需要更自由的定制,可以只取uploadPropsbuttonProps两个属性,自行组合 Ant Design 的<Upload><Button>

import { useImport } from "@refinedev/antd"; import { Upload, Button } from "antd"; export const PostList: React.FC = () => { const { buttonProps, uploadProps } = useImport(); return ( <Upload {...uploadProps}> <Button {...buttonProps}>Import</Button> </Upload> ); };

uploadProps已经替你处理了文件选择、禁用自动上传、隐藏上传列表、限定.csv扩展名等细节(见下文"返回值的内部细节"一节),因此这里只需要把属性展开即可。

Properties 配置项详解

resource

resource决定数据提供者的create/createMany方法将被调用在哪个资源上。默认情况下,它从当前 URL 路由推断资源名(antd 版本通过useResourceParams解析,见 packages/antd/src/hooks/import/index.tsx)。

useImport({ resource: "posts", });

如果你有多个同名资源,可以传入identifier而非nameidentifier只作为资源匹配的主键,而数据提供者方法仍使用<Refine/>组件中定义的name。从实现上看,antd 版本会优先取resource?.identifier ?? resource?.name传给 core 版本(见 packages/antd/src/hooks/import/index.tsx)。

关于identifier的完整说明,参考 documentation/docs/core/refine-component/index.md 中的 identifier 一节。

mapData

在将数据发送给数据提供者方法之前,如果你想对解析出的每一行做变换,可以使用mapData

useImport({ mapData: (data) => ({ ...data, category: { id: data.categoryId, }, }), });

从源码看,mapData的默认值是恒等函数(item) => item as unknown as TVariables(见 packages/core/src/hooks/import/index.tsx)。它的完整签名是MapDataFn<TItem, TVariables>,调用时会被传入(item, index, array)三个参数(见 packages/core/src/definitions/helpers/importCSVMapper/index.ts),因此你还可以基于行号或整表数据进行变换。

paparseOptions

你可以把任意 Papa Parse 配置项 传给paparseOptions

useImport({ paparseOptions: { header: true, }, });

在 core 实现中,这些选项会原样展开传给papaparse.parse(file, { complete, ...paparseOptions })(见 packages/core/src/hooks/import/index.tsx)。其类型为papaparse.ParseConfig(见 packages/core/src/hooks/import/index.tsx),常见的用法还包括自定义分隔符(delimiter)、跳过空行(skipEmptyLines)、错误处理回调(error)等。

batchSize

batchSize控制请求的批处理方式,是理解 useImport 行为的关键配置:

  • batchSize === 1:对文件中每一行调用一次create方法;
  • batchSize > 1:将数据按该大小切成多个块,对每个块调用一次createMany方法;
  • 默认值为Number.MAX_SAFE_INTEGER(见 packages/core/src/hooks/import/index.tsx),即默认一次性把全部行塞进一次createMany调用。
useImport({ batchSize: 1, });

在 core 实现中,batchSize === 1时会对每一行构造一个create.mutateAsync调用,然后通过sequentialPromises顺序执行(逐个发起请求并累计进度);否则使用lodash/chunk切块后,同样以顺序方式逐批调用createMany.mutateAsync(见 packages/core/src/hooks/import/index.tsx)。注意"顺序执行"意味着大批量文件不会并发打爆后端,但耗时也会线性增长。

如果batchSize > 1,你的数据提供者必须实现createMany,这一点在 core 的类型注释中也有明确说明(见 packages/core/src/hooks/import/index.tsx)。此外,batchSizeundefined时行为与默认一致(走createMany全量一次),这一点被测试用例专门覆盖(见 packages/core/src/hooks/import/index.spec.tsx)。

onFinish

在导入流程全部结束后触发,用于处理成功与失败的结果。回调参数是一个包含succeedederrored两个数组的对象,分别存放成功与失败请求的响应:

useImport({ onFinish: (result) => { // success requests response result.succeeded.forEach((item) => { console.log(item); }); // failed requests response result.errored.forEach((item) => { console.log(item); }); }, });

对应类型定义在 packages/core/src/hooks/import/index.tsx:

  • succeeded: ImportSuccessResult[],每个元素包含request(本次请求的原始值)、type: "success"responseTData[]);
  • errored: ImportErrorResult[],每个元素包含requesttype: "error"responseHttpError[])。

注意:使用onFinish时不要遗漏await,因为handleChange返回的是一个Promise。在测试中,onFinish也被用来断言传入数据提供者的create/createMany参数(见 packages/core/src/hooks/import/index.spec.tsx)。

meta

如果你想向create/createMany方法发送额外的元数据,可以使用meta

useImport({ meta: { foo: "bar", }, });

从 core 实现看,meta会先经过useMeta()与资源信息合并成combinedMeta,再作为meta参数传给create/createMany的 mutation(见 packages/core/src/hooks/import/index.tsx)。这在需要向 API 传递selectcustom等数据提供者特定参数时很有用。

onProgress

导入进度变化时触发的回调,参数为{ totalAmount, processedAmount },分别表示总行数与已处理行数:

useImport({ onProgress: ({ totalAmount, processedAmount }) => { // progress percentage console.log((processedAmount / totalAmount) * 100); }, });

core 版本通过useEffecttotalAmount/processedAmount状态变化时触发该回调(见 packages/core/src/hooks/import/index.tsx)。antd 版本默认会展示一个包含进度百分比的通知(环形Progress+ "Importing: x/y" 文案,完成 4.5 秒后自动关闭),你可以通过传入自定义onProgress覆盖这一默认行为(见 packages/antd/src/hooks/import/index.tsx)。在 packages/core/src/hooks/import/index.spec.tsx 中,onProgress被断言会收到{ totalAmount: 3, processedAmount: 3 }这样的最终值。

dataProviderName

当你配置了多个dataProvider时,用dataProviderName指定本次导入使用哪一个:

useImport({ dataProviderName: "second-data-provider", });

它会被原样传给 mutation 的dataProviderName参数(见 packages/core/src/hooks/import/index.tsx),适用于不同资源由不同数据提供者服务的场景。

Return Values 返回值

useImport(antd 版本)返回uploadPropsbuttonPropsisLoadingmutation。类型上,它去掉了 core 版本的handleChange/inputProps,替换为面向 Ant Design 的uploadProps/buttonProps(见 packages/antd/src/hooks/import/index.tsx)。

buttonProps

与 Ant Design<Button>组件兼容的按钮属性:

import { useImport } from "@refinedev/antd"; import { Button } from "antd"; export const PostList: React.FC = () => { const { buttonProps } = useImport(); return <Button {...buttonProps}>Import</Button>; };

内部细节:

  • type:默认为"default"
  • loading:导入进行中时为true,用于切换按钮的加载态。

(见 packages/antd/src/hooks/import/index.tsx。)

uploadProps

与 Ant Design<Upload>组件兼容的上传属性:

import { useImport } from "@refinedev/antd"; import { Upload } from "antd"; export const PostList: React.FC = () => { const { uploadProps } = useImport(); return <Upload {...uploadProps}>Import</Upload>; };

内部细节(见 packages/antd/src/hooks/import/index.tsx):

属性默认值说明
onChangehandleChange处理文件上传,触发 CSV 解析与导入流程
beforeUpload() => false阻止文件被自动上传,导入逻辑完全由本 Hook 接管
showUploadListfalse隐藏上传文件列表
accept".csv"只允许选择 CSV 文件

isLoading

布尔值,表示导入是否正在进行(见 packages/antd/src/hooks/import/index.tsx)。core 实现中它由handleChange开始时置truehandleFinishhandleCleanup时复位(见 packages/core/src/hooks/import/index.tsx)。测试中也会通过waitFor等待isLoading回到false来确认流程结束(见 packages/core/src/hooks/import/index.spec.tsx)。

mutation

useCreateuseCreateMany的结果(取决于batchSize)。core 实现中会根据batchSize === 1选择useCreate,否则选择useCreateMany(见 packages/core/src/hooks/import/index.tsx)。类型上它是两种UseMutationResult的联合(见 packages/core/src/hooks/import/index.tsx):

UseMutationResult<{ data: TData }, TError, { resource: string; values: TVariables; }, unknown> | UseMutationResult<{ data: TData[] }, TError, { resource: string; values: TVariables[]; }, unknown>

关于useCreate/useCreateMany的细节,可参考 documentation/docs/data/hooks/use-create/index.md 与 documentation/docs/data/hooks/use-create-many/index.md。

FAQ:关系数据的处理

导入时经常遇到的一个场景是:CSV 中存放的是外键 ID,而你的后端 API 要求的是嵌套对象结构。此时可以用mapData完成"扁平 CSV → 嵌套 API 结构"的还原。

假设你的 CSV 文件内容如下:

"title","content","status","categoryId","userId" "dummy title 1","dummy content 1","rejected","3","8" "dummy title 2","dummy content 2","draft","44","8" "dummy title 3","cummy content 3","published","41","10"

由于usercategory是关系字段,导出时只保存了它们的 ID(userIdcategoryId)。要基于该文件重建资源,需要把数据映射回后端 API 要求的格式:

useImport<IPostFile>({ mapData: (item) => { return { title: item.title, content: item.content, status: item.status, category: { id: item.categoryId, }, user: { id: item.userId, }, }; }, }); interface IPostFile { title: string; status: string; content: string; categoryId: string; userId: string; }

通过mapData,解析出的每行数据在发送给数据提供者之前会被转换成{ title, content, status, category: { id }, user: { id } }结构,从而满足后端 API 的约束。泛型参数TItem(此处为IPostFile)让这一变换过程具备类型安全。

从底层看,CSV 解析结果先经过importCSVMapper处理:第一行作为表头,其余每行与表头通过zip+fromPairs组合成对象,再依次交给mapData变换(见 packages/core/src/definitions/helpers/importCSVMapper/index.ts)。所以mapData收到的item已经是以表头为 key 的对象。

API Reference 速览

完整属性表

属性类型默认值说明
resourcestring从路由读取的资源名指定调用create/createMany的资源
mapDataMapDataFn<TItem, TVariables>(item) => item发送前对每行解析结果做变换
paparseOptionspapaparse.ParseConfig-传给 Papa Parse 的解析选项
batchSizenumberNumber.MAX_SAFE_INTEGER为 1 时逐行调用create,大于 1 时按批调用createMany
onFinish(results) => void-全部请求结束后回调,含succeeded/errored
metaMetaQuery-传给数据提供者方法的额外元数据
onProgress(params) => voidantd 默认进度通知进度回调,参数为{ totalAmount, processedAmount }
dataProviderNamestring-多数据提供者时指定使用哪一个

返回值表

属性说明类型
buttonProps与 Ant Design<Button>兼容的属性ButtonProps
uploadProps与 Ant Design<Upload>兼容的属性UploadProps
isLoading导入进行中的 loading 状态boolean
mutation创建导入资源的 mutation/mutations 结果UseMutationResult<{ data: TData }, ...>|UseMutationResult<{ data: TData[] }, ...>(见上文"mutation"一节)

类型参数

类型参数说明默认值
TItem解析后的 CSV 数据接口any
TData数据查询结果类型,继承BaseRecordBaseRecord
TError自定义错误对象,继承HttpErrorHttpError
TVariablesmutation 函数的入参类型any

从源码看完整导入流程

把上面所有信息串起来,一次导入的完整生命周期如下(依据 packages/core/src/hooks/import/index.tsx 的handleChange实现):

  1. 触发:antd 的<Upload>触发onChange(即handleChange),beforeUpload返回false阻止自动上传;
  2. 重置状态handleCleanuptotalAmount/processedAmount归零、isLoadingtrue
  3. 解析papaparse.parse读取文件,complete回调拿到原始二维数组;importCSVMapper依据表头将其转换为对象数组,并应用mapData(见 packages/core/src/definitions/helpers/importCSVMapper/index.ts);
  4. 分批totalAmount记录总行数;若batchSize === 1则每行一个请求(create),否则用lodash/chunk切块后每块一个请求(createMany),并通过sequentialPromises顺序执行,每完成一个请求/批次就更新processedAmount,从而驱动onProgress
  5. 收尾:所有请求完成后,按type拆分为succeeded/errored传给onFinishisLoading复位;antd 版本在完成 4.5 秒后关闭默认进度通知。

对应行为都有测试覆盖,例如 packages/core/src/hooks/import/index.spec.tsx 验证了batchSize: 2createMany按两行一批被调用、batchSize: 1create逐行被调用、mapData在请求前生效、onFinish能收到成功/失败结果、onProgress收到最终进度等。antd 侧的<ImportButton />测试则直接复用@refinedev/ui-testsbuttonImportTests(见 packages/antd/src/components/buttons/import/index.spec.tsx),保证组件与 Hook 的行为稳定。

小结

useImport将"选择 CSV → 解析 → 变换 → 分批写入数据提供者 → 展示进度与结果"这条完整链路封装在一个 Hook 中:配置层面有resourcemapDatapaparseOptionsbatchSizeonFinishmetaonProgressdataProviderName八个选项;返回层面有buttonPropsuploadPropsisLoadingmutation四个值,与 Ant Design 组件开箱即用。理解其"batchSize 决定 create / createMany 切换、sequentialPromises 顺序执行、mapData 在 importCSVMapper 内按行变换"这三条实现事实,能帮助你在处理大文件批导入、多数据提供者、关系数据还原等真实场景时做出正确的配置决策。

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

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

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

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

立即咨询