当前位置: 首页 > news >正文

解决 Semi Design Upload 组件实现自定义压缩,上传文件后无法触发 onChange

背景

我们团队主要在做 C 端产品,对于 C 端应用,图片资源使用 CDN 十分重要,因此我们曾建立了一个文件上传平台:上传文件后,可以复制图片的 CDN URL 在前端项目中使用。

目前服务端不会对图片做压缩,使用前得先借助其他工具手动压缩再上传,体验很差。调研后发现,纯前端就能完成压缩且完全满足需求,于是决定给 Upload 组件加上「自动压缩」能力。

实现思路

整体流程:

  1. 用户多选文件 → 文件列表展示文件名、状态、预览图等
  2. 异步批量压缩图片,实时更新状态: 压缩中 → 已压缩,等待上传 / 压缩失败,使用原文件
  3. 用户点击「开始上传」→ 状态变为 上传中 → 上传成功 / 失败

Semi Design 的 Upload 组件有两处可切入压缩逻辑:

方案 入口 说明
transformFile 选中后、上传前触发,返回新 FileItem
onChange 文件状态变化回调,可完全自定义流程

需要展示「压缩中」「压缩失败」等中间态,因此选用更灵活的 方案②

关键类型

Upload 组件的 FileItem 类型包含了一些信息,因此我通过更新 FileItem 对象的属性可以很容易实现文件列表的状态更新。

interface FileItem {event? : event,  // xhr eventfileInstance?: File, // original File Object which extends Blob, 浏览器实际获取到的文件对象(https://developer.mozilla.org/zh-CN/docs/Web/API/File)name: string,percent? : number, // 上传进度百分比preview: boolean, // 是否根据url进行预览response?: any, // xhr的response, 请求成功时为respoonse body,请求失败时为对应 errorshouldUpload?: boolean; // 是否应该继续上传showReplace?: boolean, // 单独控制该file是否展示替换按钮showRetry?: boolean, // 单独控制该file是否展示重试按钮size: string, // 文件大小,单位kbstatus: string, // 'success' | 'uploadFail' | 'validateFail' | 'validating' | 'uploading' | 'wait';uid: string, // 文件唯一标识符,如果当前文件是通过upload选中添加的,会自动生成uid。如果是defaultFileList, 需要自行保证不会重复url: string,validateMessage?: ReactNode | string,
}

代码实现

<Upload// other propscustomRequest={customRequest}fileList={storeFileList}onChange={onChange}/>

传入 fileList 表示组件处在受控模式。

  const onChange = async ({ fileList }: OnChangeProps) => {const newFileList = uniqBy(fileList, (file) => file.name);actions.batchUpdate({ fileList: newFileList });const filesToCompress = getFilesToCompress(newFileList);if (filesToCompress.length === 0) {return;}for (const file of filesToCompress) {updateFileList(file.name, {status: "validating",validateMessage: "压缩中...",});}const compressionPromises = files.map(async (file) => {try {const compressedFile = await compressImage(file, 80);updateFileList(file.name, {fileInstance: compressedFile,status: "wait",validateMessage: "已压缩,等待上传",});} catch {updateFileList(file.name, {status: "wait",validateMessage: "压缩失败,使用原文件",});}});await Promise.allSettled(compressionPromises);};

iShot_2025-10-22_16.31.44

为了演示效果,compressImage 里故意 sleep 了 2s。

踩坑:onSuccess 后 onChange 未触发

文件上传成功后,我们在 customRequest 里调用 onSuccess 期望组件自动把状态改为 success,但 onChange 居然没执行!

const customRequest = ({ file, onProgress, onError, onSuccess }) => {batchUpload(file.fileInstance).then(res => {if (res.data.code === '0') onSuccess(res, file.event!);// 其他逻辑}).catch(onError);
};

排查过程

image

经过几轮友好 AI 对话,它总往错误方向跑,即使我告诉它去源码中寻找答案。看来只能动动小手了。

semi 有两个概念:

  • Foundation:Foundation 包含最能代表 Semi Design 组件交互的业务逻辑,包括 UI 行为触发后的各种计算、分支判断等逻辑,它并不直接操作或者引用 DOM,任意需要 DOM 操作,驱动组件渲染更新的部分会委派给 Adapter 执行。
  • Adapter:Adapter 是一个接口,具有 Foundation 实现 Semi Design 业务逻辑所需的所有方法,并负责 1. 组件 DOM 结构声明 2.负责所有跟 DOM 操作/更新相关的逻辑,通常会使用框架 API 进行 setState、getState、addEventListener、removeListener 等操作。适配器可以有许多实现,允许与不同框架的互操作性。

所以我们可以直接去 upload/foundation.ts 中查看实现逻辑。

image

handleSuccess 中有调用 notifyChange, notifyChange 就是 props.onChange

image

我们还看到在handleSuccess 中有一个判断, 如果未找到文件就 return, 也就不会调用 onChange 了。我们继续查看判断中用到的 _getFileIndex 函数

image

它是通过对比 File 对象的 uid 属性实现的,浏览器原生 File 对象是没有 uid 属性的,那么这个 uid 是哪里来的呢?也是 upload/foundation.ts 中添加的。

image

到此,我们可以大胆猜测,我们在上传文件后,调用 onSuccess,走到 handleSuccess 逻辑被提前 return 了,原因就是上传的文件对象中没有 uid 或者 uid 不一致。我们在代码中断点看一下:

image

的确,File 对象中并没有 uid 字段。 我们把压缩逻辑注释掉,继续断点并拿 fileInstance 对比下:

image

对比得知,压缩后的 File 对象中没有 uid 字段,这证实了我们之前的猜想。排查过程总结如下:

  1. 翻 Semi 源码(upload/foundation.ts) handleSuccessnotifyChange(props.onChange),但前面有一行:“找不到文件就提前 return”。
  2. _getFileIndex 通过 uid 匹配文件。原生 File 对象没有 uid,是 Semi 在添加文件时动态挂上去的。
  3. 我们压缩后把 fileInstance 整个替换成了新 File,导致 uid 丢失,于是 _getFileIndex 返回 -1 → 直接 return → onChange 永远不会触发。

解法:把 uid 还回去

解决这个问题非常简单, 由于 uid 是本来就有的,只是我们对文件压缩后给丢了, 所以文件压缩后再还回去就可以了。

image

iShot_2025-10-22_17.46.40

http://www.hskmm.com/?act=detail&tid=36792

相关文章:

  • QT实现QTreeWidget项目拖拽移动功能
  • lora学习笔记
  • 自动化释放5G全部潜力:新西兰电信One NZ的实践之路
  • cookie机制如何获取用户个人信息
  • 数据库——聚合函数
  • 第二十一篇
  • linux服务器操作系统字符集是GBK,tomcat和部署的程序是UTF-8,启动后应用界面乱码如何解决
  • DEIMv2浅读
  • 阿里出手了:全免费!号称国内版ClaudeCode?
  • [MS-DOS]MS-DOS 6.22 with CD-ROM Driver.ver.6.22.English下载与安装
  • 完整教程:2- 十大排序算法(希尔排序、计数排序、桶排序)
  • 分词器模型
  • 2025 年国内品牌设计公司最新推荐排行榜:聚焦行业领军者优势,精选优质服务商深度解析
  • 报考PostgreSQL中级认证证书多少钱?
  • 2025 年 LFT 材料源头厂家最新推荐权威榜单:复合 / 注塑 / 增强 / 轻量化 / 长碳纤 / 长玻纤 / 耐高温 LFT 材料优质公司推荐
  • Windows Server 2025 安装IIS服务
  • 易路薪酬能力深度解析:以科技赋能企业薪酬管理新范式
  • LaTeX 项目结构优化:从基础到专业
  • Java的优势有哪些
  • 传感类语音提示器语音播报芯片最佳适配方案WT2003H
  • 集合中的贡献法
  • 广州治疗青少年心理医院口碑榜:TOP3医疗机构专业实力深度解析
  • 人狗大战Ⅱ
  • 【IEEE出版、往届会后3个月检索】2025 第九届控制工程与先进算法国际论坛(IWCEAA 2025)
  • 整装定制家具生产厂家口碑榜:TOP3企业智能制造实力深度解析
  • 实用指南:阿里云安装Docker
  • 高性能超低功耗蓝牙电子价签方案 OM6626 NRF52832
  • 软工第三次作业-结对项目
  • 给大家分享三个特别好用的在线工具,可以为你的工作节省很多时间
  • 2025 年振动筛源头厂家最新推荐榜单:权威甄选实验 / 防爆 / 精细筛分设备,揭秘靠谱供应企业