refine v3 Base64 文件上传:在表单提交前将文件转换为 Base64 字符串
2026/9/13 2:07:59 网站建设 项目流程

refine v3 Base64 文件上传:在表单提交前将文件转换为 Base64 字符串

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

本篇技术指南介绍 refine(v3)Advanced Tutorials 中 "Base64 Upload" 的完整实现:利用 Ant Design<Form>组件的onFinish属性,在表单提交前把Upload控件中的文件逐一转换为 Base64 字符串,再连同表单数据一起交给 refine 的useForm提交流程。读完后,你将掌握file2Base64getValueFromEvent两个工具函数的用法、beforeUpload={() => false}的拦截机制,以及如何在 Create / Edit 两种页面中复用同一套 Base64 上传逻辑。

为什么需要 Base64 上传

很多后端接口并不直接接收multipart/form-data中的二进制文件,而是期望在 JSON 请求体中携带文件的 Base64 编码字符串(例如data:image/png;base64,iVBORw0KGgo...)。典型场景包括:

  • 后端通过 JSON 字段直接落库或推送到对象存储(如某些 Strapi、Supabase 集成场景);
  • 网关或反序列化层只接受 JSON,不接受混合内容的 multipart 请求;
  • 头像、附件等小体积文件,开发者希望在一次POST中完成"业务数据 + 文件"的整体提交。

refine 的解决方案是:在表单onFinish触发时、真正调用 mutation 之前,把Form收集到的UploadFile[]转换为携带base64String字段的对象数组。官方文档documentation/versioned_docs/version-3.xx.xx/advanced-tutorials/upload/base64-upload.md明确给出了这一思路:"By encoding your files and images from your forms to Base64 you can change all files needed for the upload to Base64 format before the submit"

完整示例:Create 页面中的 Base64 上传

以下代码完整继承自官方文档,上传字段为avatar(多文件拖拽上传):

// pages/users/create.tsx import { //highlight-start file2Base64, //highlight-end } from "@pankod/refine-core"; import { Create, Form, Upload, Input, useForm, // highlight-start getValueFromEvent, // highlight-end } from "@pankod/refine-antd"; export const UserCreate: React.FC = () => { const { form, formProps, saveButtonProps } = useForm<IUser>(); return ( <Create saveButtonProps={saveButtonProps}> <Form {...formProps} layout="vertical" // highlight-start onFinish={async (values) => { const base64Files = []; // @ts-ignore const { avatar } = values; for (const file of avatar) { if (file.originFileObj) { const base64String = await file2Base64(file); base64Files.push({ ...file, base64String, }); } else { base64Files.push(file); } } return ( formProps.onFinish && formProps.onFinish({ ...values, avatar: base64Files, }) ); }} // highlight-end > <Form.Item label="First Name" name="firstName" rules={[ { required: true, }, ]} > <Input /> </Form.Item> <Form.Item label="Avatar"> <Form.Item name="avatar" valuePropName="fileList" // highlight-start getValueFromEvent={getValueFromEvent} noStyle rules={[ { required: true, }, ]} > <Upload.Dragger listType="picture" multiple // highlight-start beforeUpload={() => false} > <p className="ant-upload-text"> Drag & drop a file in this area </p> </Upload.Dragger> </Form.Item> </Form.Item> </Form> </Create> ); }; interface IUser { id: number; firstName: string; avatar: [ { uid: string; name: string; url: string; status: "error" | "success" | "done" | "uploading" | "removed"; }, ]; }

核心代码逐段解析

1. 用onFinish包裹并替换提交流程

关键点在于<Form {...formProps}>useForm返回的formProps中已经内置了一个onFinish,它负责把校验通过的表单值交给useForm内部的 mutation(onMutate)。这里的做法是在展开formProps之后再显式传入自定义onFinish,让它覆盖(wrapper)掉默认实现:

  1. 遍历values.avatar(即 antd 的UploadFile[]);
  2. 对每个文件调用await file2Base64(file),得到 DataURL 形式的 Base64 字符串;
  3. 生成base64Files数组,其中每个元素在原UploadFile基础上追加base64String字段;
  4. 最后调用formProps.onFinish?.({...values, avatar: base64Files}),把转换后的完整表单值交还 refine 的提交流程。

这样dataProvider最终收到的variables中,avatar就是带base64String的对象数组,后端可直接从 JSON 中解码保存。

2.originFileObj:区分"新文件"与"已有文件"

if (file.originFileObj) { const base64String = await file2Base64(file); base64Files.push({ ...file, base64String }); } else { base64Files.push(file); }

originFileObj是 antdUpload为"本次会话中用户新选择的原始File对象"保留的引用。file2Base64内部依赖FileReader.readAsDataURL(file.originFileObj as Blob),只有持有原始Blob才能读取二进制内容。对于从后端加载回来的已有文件(例如编辑页回显的 URL),没有originFileObj,也无需重新编码——直接透传即可。这也是为什么示例在 Edit 场景下可以"不改剩余代码"工作(见下文"Create 与 Edit 复用"一节)。

3.getValueFromEventvaluePropName="fileList"

内层Form.Item上的两个属性是 antd 上传控件接入 Form 的标准姿势:

  • valuePropName="fileList":告诉 Form 用fileList属性而非默认的value绑定子组件;
  • getValueFromEvent={getValueFromEvent}:把UploadonChange事件参数(UploadChangeParam)归一化为UploadFile[]存进表单值。

refine-antd 包对该函数的实现非常简洁,可以直接在源码中确认(packages/antd/src/definitions/upload/index.ts):

import type { UploadFile, UploadChangeParam } from "antd/lib/upload/interface"; export const getValueFromEvent = (event: UploadChangeParam): UploadFile[] => { const { fileList } = event; return [...fileList]; };

即:从事件参数中取出fileList并浅拷贝后作为表单值返回,保证Form拿到的是不可变的新数组引用,从而正确触发受控更新。

4.beforeUpload={() => false}:拦截自动上传

<Upload.Dragger>上的beforeUpload={() => false}是 antd 的约定:返回false会阻止控件自身发起任何网络上传请求。这很重要——文件只停留在前端内存中,真正的"提交"由 refine 的onFinish统一完成。若漏写这一行,antd 会尝试按其默认action单独上传文件,导致提交行为失控。

源码级原理:file2Base64是怎么工作的

file2Base64定义在 core 包的upload定义目录中,实现如下(packages/core/src/definitions/upload/file2Base64/index.ts):

export const file2Base64 = (file: any): Promise<string> => { return new Promise((resolve, reject) => { const reader = new FileReader(); const resultHandler = () => { if (reader.result) { reader.removeEventListener("load", resultHandler, false); resolve(reader.result as string); } }; reader.addEventListener("load", resultHandler, false); reader.readAsDataURL(file.originFileObj as Blob); reader.onerror = (error) => { reader.removeEventListener("load", resultHandler, false); return reject(error); }; }); };

从源码结构看,有三个值得注意的实现细节:

  1. 基于FileReader.readAsDataURLreader.result是完整的 DataURL 字符串(含data:<mime>;base64,前缀),因此后端拿到的base64String可直接解析出 MIME 类型与编码内容,无需额外传输 MIME;
  2. 输入约定是 antd 风格的UploadFile:函数签名虽然是file: any,但实现里直接读取file.originFileObj。这与示例中传入选中的 antd 文件对象保持一致——如果你传入原生File对象,originFileObjundefinedreadAsDataURL(undefined)将无法正常读取;
  3. 事件监听的生命周期管理load成功后会removeEventListener,错误分支同样移除监听并reject,避免 Promise 悬挂或监听器泄漏。

该函数的单元测试位于 packages/core/src/definitions/upload/file2Base64/index.spec.ts,通过 mockwindow.FileReader验证了成功路径:readAsDataURL触发addEventListener注册的回调后,file2Base64正确 resolve 出reader.result(测试断言expect(content).toBe("file content"))。

值得一提的是,@pankod/refine-antd也内置了一份等价实现(packages/antd/src/definitions/upload/index.ts),只是参数类型标注为UploadFile。文档示例选择从 core 包导入,保持了对具体 UI 框架包的最小依赖面。

Create 与 Edit 复用:改一个组件即可

官方文档特别提示:<Create>换成<Edit>,其余代码一行不改即可得到编辑页的 Base64 上传。原因在于:

  • useForm在 Edit 模式下会自动query当前记录并把返回值作为表单初始值注入;
  • 回显出来的已有文件对象没有originFileObj,走else分支原样透传;
  • 用户在编辑页新添加的文件带有originFileObj,会被转换为base64String一并提交。

仓库中的示例工程 examples/upload-antd-base64 完整演示了这一模式:create.tsx 与同目录下的 edit.tsx 使用了完全相同的onFinish转换逻辑。该示例中还定义了表单变量的类型约束(examples/upload-antd-base64/src/interfaces/index.d.ts):

export interface IUserVariable { id: number; email: string; firstName: string; lastName: string; avatar: UploadFile[]; }

avatar显式标注为UploadFile[]后,useForm<IUser, HttpError, IUserVariable>的第三个泛型即可精确描述提交变量结构,避免文档示例中// @ts-ignore的权宜写法。

注意事项与适用边界

  • Base64 体积膨胀:Base64 编码会使数据体积增加约 33%(每 3 字节变 4 字符)。该方案适合头像、图标等中小体积文件;大文件或批量文件建议改用 multipart/form-data 上传,refine 在 multipart-upload.md 中提供了对应的进阶教程;
  • 浏览器 API 依赖file2Base64依赖FileReader,只能在浏览器(或具备 DOM 环境的测试运行时)中使用;
  • 包名适用前提:本文对应 refine v3 文档(versioned_docs/version-3.xx.xx),包名为@pankod/refine-core/@pankod/refine-antd;仓库中的examples/upload-antd-base64示例已升级到@refinedev/*命名,逻辑完全一致,迁移时只需替换导入路径;
  • 请求体大小限制:Base64 字符串走 JSON 请求体,注意后端网关、Nginx 等中间件对Content-Length的限制配置。

小结

Base64 上传的本质是一次"提交前的数据整形":借助 antdFormonFinish覆盖机制,把Upload组件收集到的UploadFile[]逐个通过file2Base64转换为携带base64String的纯数据,再交还useForm的 mutation 流程。整套方案只需要一个异步onFinish、两个 refine 工具函数(file2Base64getValueFromEvent)和一个beforeUpload={() => false}拦截,即可让 Create / Edit 两种页面以同一套代码完成 Base64 文件提交。

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

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

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

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

立即咨询