Refine 中实现 Multipart 文件上传Ant Design Upload 集成、上传端点设计与 useFileUploadState 源码解析【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine本篇技术文章围绕 Refine 官方文档中的 Multipart Upload 指南展开讲解如何在 Refine Ant Design 管理后台中实现文件的多部件multipart/form-data上传包括在创建/编辑表单中接入Upload.Dragger、服务端上传端点的请求与响应契约、表单提交时图片数据的流转方式以及利用useFileUploadStateHook 在上传过程中禁用保存按钮。读完本文你将掌握一套可直接落地的 Refine 文件上传方案并能从源码层面理解getValueFromEvent、useFileUploadState、useApiUrl三个关键符号的实际实现。什么是 Multipart 上传Multipart 请求是 HTTP 客户端用来向服务器发送文件和数据的一类请求浏览器和各类 HTTP 客户端上传文件时普遍采用这种机制。与 Base64 内联上传不同multipart 上传把二进制文件以multipart/form-data的编码形式单独发送到上传专用端点文件不经过业务数据的 CRUD 请求通道而是先落到媒体存储或对象存储中拿到一个可访问 URL业务表单中只保存 URL 等元数据。Refine 官方给出的完整指南位于 multipart-upload.md配套的可运行示例工程位于 examples/upload-antd-multipart下文将完整继承该文档的操作步骤并结合当前仓库的包源码进行纵深扩充。整体流程分为三段创建/编辑表单通过 Ant Design 的Upload.Dragger组件接收文件并以 multipart/form-data 方式把文件直接 POST 到上传端点上传端点服务端接收file二进制字段保存文件后返回{ url: ... }表单提交useForm提交时把UploadFile对象数组含 uid、name、url、status 等字段作为普通 JSON 字段随业务数据一起发送。第一步在创建表单中加入图片上传字段以创建文章post页面为例需要在标题字段之外增加一个图片字段。文档给出的完整代码如下源自版本 3 文档包名为pankod/refine-core与pankod/refine-antd对应章节后文会给出当前仓库中的包名映射说明import { // highlight-start useApiUrl, // highlight-end } from pankod/refine-core; import { // highlight-start Upload, getValueFromEvent, // highlight-end Create, Form, Input, useForm, } from pankod/refine-antd; export const PostCreate: React.FC () { const { formProps, saveButtonProps } useFormIPost(); // highlight-next-line const apiUrl useApiUrl(); return ( Create saveButtonProps{saveButtonProps} Form {...formProps} layoutvertical Form.Item labelTitle nametitle rules{[ { required: true, }, ]} Input / /Form.Item Form.Item labelImage Form.Item nameimage valuePropNamefileList // highlight-next-line getValueFromEvent{getValueFromEvent} noStyle // highlight-start Upload.Dragger namefile action{${apiUrl}/media/upload} listTypepicture maxCount{5} multiple p classNameant-upload-text Drag drop a file in this area /p /Upload.Dragger // highlight-end /Form.Item /Form.Item /Form /Create ); }; interface IPost { id: number; title: string; image: [ { uid: string; name: string; url: string; status: error | success | done | uploading | removed; }, ]; }关键属性逐项说明useApiUrlRefine core 提供的 Hook用于拿到当前 dataProvider 配置的 API 基础地址从而拼出上传端点${apiUrl}/media/upload。文档提示可以用useApiUrlHook 获取 API URL。从当前仓库源码看它的实现非常薄useApiUrl.ts 中先通过useDataProvider拿到 dataProvider 实例再调用其getApiUrl()方法返回基础 URL并支持按resource.meta.dataProviderName选择命名 dataProvider。这意味着上传端点地址会自动跟随你在Refine dataProvider{...}中配置的 API 域名变化。Upload.Dragger的action这是 multipart 上传的核心——文件不经过表单提交逻辑而是由 Ant Design Upload 在文件选择后立即向该地址发起multipart/form-data的 POST 请求。action中填写的正是后文要定义的上传端点地址。namefile指定 multipart 请求体中文件字段的字段名服务端按此字段名解析二进制内容。listTypepicture以图片墙形式展示已上传文件配合图片类资源更直观。maxCount{5}与multiple限制最多 5 个文件并允许多选。valuePropNamefileListgetValueFromEventForm.Item通过这两个配置把 Ant Design Upload 的受控属性对齐到表单值上并把组件的 change 事件参数转换成UploadFile[]存入表单字段image。getValueFromEvent 的源码实现文档特别提醒必须使用getValueFromEvent方法把上传得到的文件转换为 Antd 的UploadFile对象。当前仓库中它的实现位于 upload/index.ts逻辑一目了然import type { UploadFile, UploadChangeParam } from antd/lib/upload/interface; export const getValueFromEvent (event: UploadChangeParam): UploadFile[] { const { fileList } event; return [...fileList]; };即直接取 change 事件参数中的fileList并拷贝为数组返回保证表单拿到的是 Antd 定义的UploadFile结构而不是原始File对象。同一文件里还导出了file2Base64工具函数基于FileReader.readAsDataURL它服务于另一种上传方式——base64 上传可作为了解 multipart 与 base64 两条路线差异的对照参考。当前仓库中配套的完整示例工程 examples/upload-antd-multipart/src/pages/posts/create.tsx 与该文档代码结构一致额外带上了 category、status、content 字段其图片字段部分同样是valuePropNamefileListgetValueFromEventUpload.Dragger的组合且使用当前包名refinedev/core、refinedev/antd与antd包。第二步设计服务端上传端点表单中action指向的地址需要一个真实存在的上传端点来承接 multipart 请求。文档给出的端点契约如下。请求形态{ file: binary }:::caution 该端点必须接受Content-Type: multipart/form-data且文件字段名为Form Data: file: binary与Upload.Dragger上的namefile一一对应。 :::服务端处理完成后端点应返回一个包含下载 URL 的对象{ url: https://example.com/uploaded-file.jpeg }也就是说端点的职责是接收 multipart 二进制文件 - 持久化磁盘或对象存储- 返回{ url }。Ant Design 的 Upload 组件拿到响应后会据此补全对应UploadFile的url字段并更新为status: done。示例工程 examples/upload-antd-multipart/src/App.tsx 中配置的API_URL https://api.fake-rest.refine.dev正是文档中该端点的宿主dataProvider(API_URL)通过refinedev/simple-rest注入useApiUrl返回的就是这个域名。第三步理解表单提交时的数据结构当用户在创建页点击保存、表单真正提交时useForm会把整个表单值 POST 到业务资源端点其中image字段就是由getValueFromEvent维护的UploadFile对象数组{ title: Test, image: [ { uid: rc-upload-1620630541327-7, name: greg-bulla-6RD0mcpY8f8-unsplash.jpg, url: https://refine.ams3.digitaloceanspaces.com/78c82c0b2203e670d77372f4c20fc0e2, type: image/jpeg, size: 70922, percent: 100, status: done } ] }文档明确要求以下 Antd Upload 组件的字段是必需的保存时必须全部落库PropertyDescriptionuid唯一标识Unique idname文件名File Nameurl下载 URLDownload URLstatus取值error, success, done, uploading, removed这份字段定义与当前仓库中的类型声明完全对应。packages/antd/src/interfaces/upload.ts 中定义了UploadedFile接口export interface UploadedFile { uid: string; name: string; url: string; type: string; size: number; percent: number; status: error | success | done | uploading | removed; }示例工程的实体类型 examples/upload-antd-multipart/src/interfaces/index.d.ts 也按image: UploadFile[]声明了IPost.image与文档中的interface IPost语义一致。status的五个枚举值覆盖了上传全生命周期uploading传输中、done/success完成、error失败、removed被用户移除这也是下一步上传状态功能能感知进度的基础。编辑表单预填充已有图片并回写编辑页的逻辑与创建页基本相同区别在于表单初始值来自GET请求。文档给出的编辑页代码如下import { // highlight-start useApiUrl, // highlight-end } from pankod/refine-core; import { // highlight-start Upload, getValueFromEvent, // highlight-end Edit, Form, Input, useForm, } from pankod/refine-antd; export const PostEdit: React.FC () { const { formProps, saveButtonProps } useFormIPost(); // highlight-next-line const apiUrl useApiUrl(); return ( Edit saveButtonProps{saveButtonProps} Form {...formProps} layoutvertical Form.Item labelTitle nametitle rules{[ { required: true, }, ]} Input / /Form.Item Form.Item labelImage Form.Item nameimage valuePropNamefileList getValueFromEvent{getValueFromEvent} noStyle // highlight-start Upload.Dragger namefile action{${apiUrl}/media/upload} listTypepicture maxCount{5} multiple p classNameant-upload-text Drag drop a file in this area /p /Upload.Dragger // highlight-end /Form.Item /Form.Item /Form /Edit ); };编辑场景下的数据流是读取useForm自动发起GET /posts/1返回体中携带既有的image数组结构与上文的UploadFile相同{ id: 1, title: Test, image: [ { uid: rc-upload-1620630541327-7, name: greg-bulla-6RD0mcpY8f8-unsplash.jpg, url: https://refine.ams3.digitaloceanspaces.com/78c82c0b2203e670d77372f4c20fc0e2, type: image/jpeg, size: 70922, percent: 100, status: done } ] }预填充由于image字段本身就是UploadFile[]结构Antd Upload 可以直接根据url、name、status: done渲染出既有图片的缩略图用户可以继续追加或删除。回写表单提交时以PUT /posts/1发送完整的image数组{ title: Test, image: [ { uid: rc-upload-1620630541327-7, name: greg-bulla-6RD0mcpY8f8-unsplash.jpg, url: https://refine.ams3.digitaloceanspaces.com/78c82c0b2203e670d77372f4c20fc0e2, type: image/jpeg, size: 70922, percent: 100, status: done } ] }正因为创建、编辑两端的数据结构完全同构都是UploadFile[]同一套valuePropNamefileListgetValueFromEvent配置无需任何改动即可复用到编辑页——这也是把UploadFile全量字段落库而非只存 url 字符串的收益所在。进阶上传进行中禁用保存按钮useFileUploadState文档的 Uploading State 一节指出你很可能希望在文件还在上传时禁用表单的保存按钮以避免把status: uploading、url尚未补全的数据提交给后端。Refine antd 包为此提供了useFileUploadStateHookimport { useApiUrl } from pankod/refine-core; import { Upload, getValueFromEvent, // highlight-next-line useFileUploadState, Create, Form, Input, useForm, } from pankod/refine-antd; export const PostCreate: React.FC () { const { formProps, saveButtonProps } useFormIPost(); // highlight-next-line const { isLoading, onChange } useFileUploadState(); const apiUrl useApiUrl(); return ( Create // highlight-start saveButtonProps{{ ...saveButtonProps, disabled: isLoading, }} // highlight-end Form {...formProps} layoutvertical Form.Item labelTitle nametitle rules{[ { required: true, }, ]} Input / /Form.Item Form.Item labelImage Form.Item nameimage valuePropNamefileList getValueFromEvent{getValueFromEvent} noStyle Upload.Dragger namefile action{${apiUrl}/media/upload} listTypepicture maxCount{5} multiple // highlight-next-line onChange{onChange} p classNameant-upload-text Drag drop a file in this area /p /Upload.Dragger /Form.Item /Form.Item /Form /Create ); };用法上只有两处接入点把onChange挂到Upload.Dragger上监听上传进度变化再把isLoading合并进saveButtonProps.disabled。Hook 的源码实现该 Hook 的实现位于 useFileUploadState/index.ts核心逻辑是把 Antd 的UploadChangeParam中每个文件的status映射为布尔值const mapStatusToLoading (files: UploadChangeParam[fileList]) { return files.map((file) { switch (file.status) { case uploading: return true; default: return false; } }); };只要文件列表中任意一个文件处于uploading状态isLoading即为true全部到达终态done/success/error/removed后恢复为false。Hook 用useCallback稳定onChange引用、用useMemo缓存返回值避免不必要的重渲染。它的行为由单元测试固化useFileUploadState/index.spec.ts 验证了两条路径——初始状态下isLoading为false当onChange收到包含status: uploading的fileList时isLoading变为true。包名与版本注意事项本文继承的原始指南位于版本 3 文档目录version-3.xx.xx/advanced-tutorials/upload/multipart-upload.md其中代码使用pankod/refine-core、pankod/refine-antd包名且Upload组件由 antd 集成包导出。从当前仓库结构看packages 目录下的 antd 集成包为 packages/antd示例工程 examples/upload-antd-multipart 已迁移到refinedev/core、refinedev/antd包名并从antd包直接引入Form、Input、Upload。从源码结构看两种写法对应的组件能力一致getValueFromEvent、useFileUploadState等符号在 packages/antd/src/definitions/upload/index.ts 与 packages/antd/src/hooks/useFileUploadState/index.ts 中均可定位到实现。若你在新项目中按当前版本搭架应使用示例工程中的refinedev/*包名若维护 v3 老项目则按原文档的pankod/*包名即可。小结与延伸阅读上传组件侧Upload.Dragger的action指向 multipart 端点namefile定义字段名valuePropNamefileListgetValueFromEvent把UploadFile[]同步进表单服务端侧端点必须声明Content-Type: multipart/form-data、按字段名file接收二进制并返回{ url: ... }数据契约uid、name、url、status四个字段连同 type/size/percent需要全量落库以保证编辑页能正确预渲染图片墙体验细节用useFileUploadState的isLoading在上传未完成时禁用保存按钮防止半完成状态被提交。相关仓库资源原始文档multipart-upload.md可运行示例工程examples/upload-antd-multipart/src/pages/posts/create.tsx、examples/upload-antd-multipart/src/pages/posts/edit.tsx、examples/upload-antd-multipart/src/App.tsx核心实现packages/antd/src/definitions/upload/index.ts、packages/antd/src/hooks/useFileUploadState/index.ts、packages/antd/src/interfaces/upload.ts、packages/core/src/hooks/data/useApiUrl.ts测试用例packages/antd/src/hooks/useFileUploadState/index.spec.ts【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考