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

发布时间:2026/9/10 17:20:38

解决 Semi Design Upload 组件实现自定义压缩,上传文件后无法触发 onChange 解决 Semi Design Upload 组件实现自定义压缩上传文件后无法触发 onChange一、问题背景Semi Design Upload 组件的基本使用Semi Design 是字节跳动开源的企业级 UI 组件库其 Upload 组件提供了丰富的文件上传功能。在实际开发中我们经常需要在文件上传前对图片进行压缩处理以减少带宽消耗和提升用户体验。然而当我们在 Upload 组件的beforeUpload钩子中实现自定义压缩后常常会发现一个棘手的问题压缩后的文件上传后onChange事件无法正常触发。### 1.1 基本 Upload 组件示例让我们先看一个最简单的 Upload 组件使用示例javascriptimport React from react;import { Upload, Button } from douyinfe/semi-ui;function BasicUpload() { const handleChange ({ fileList, file }) { console.log(文件变化:, fileList); }; return ( Upload actionhttps://example.com/upload onChange{handleChange} Button上传文件/Button /Upload );}在这个基础示例中onChange能正常触发因为 Upload 组件内部管理了文件列表的状态变化。但当我们引入自定义压缩逻辑后问题就出现了。## 二、问题深入自定义压缩导致 onChange 失效的原因当我们使用beforeUpload钩子对文件进行压缩时通常会返回一个新的 File 对象。这个新对象与原始文件在内存引用上不同导致 Upload 组件内部的状态管理逻辑无法正确识别文件变化。### 2.1 自定义压缩的常见实现javascriptimport React from react;import { Upload, Button } from douyinfe/semi-ui;function CompressUpload() { // 自定义压缩函数 const compressImage (file) { return new Promise((resolve) { const reader new FileReader(); reader.readAsDataURL(file); reader.onload (e) { const img new Image(); img.src e.target.result; img.onload () { const canvas document.createElement(canvas); const ctx canvas.getContext(2d); // 压缩参数宽高缩小为一半 canvas.width img.width / 2; canvas.height img.height / 2; ctx.drawImage(img, 0, 0, canvas.width, canvas.height); // 转换为 Blob canvas.toBlob((blob) { // 创建新的 File 对象 const compressedFile new File( [blob], file.name, { type: file.type, lastModified: Date.now() } ); resolve(compressedFile); }, file.type, 0.8); // 质量参数 0.8 }; }; }); }; const handleBeforeUpload async ({ file, fileList }) { if (file.type.startsWith(image/)) { const compressed await compressImage(file); return compressed; // 返回新文件 } return file; }; const handleChange ({ fileList }) { console.log(文件列表:, fileList); // 问题这里可能不会触发 }; return ( Upload actionhttps://example.com/upload beforeUpload{handleBeforeUpload} onChange{handleChange} Button上传并压缩/Button /Upload );}这个实现看似合理但实际运行时onChange可能无法正常触发。这是因为beforeUpload返回的新 File 对象与原始文件在 Upload 组件内部的文件列表管理中存在差异。## 三、解决方案手动管理文件列表与 onChange 触发要解决这个问题我们需要理解 Upload 组件的内部机制。核心思路是在beforeUpload中不仅返回压缩后的文件还要手动更新文件列表并触发onChange。### 3.1 完整的解决方案代码下面是经过验证的完整解决方案包含详细的注释javascriptimport React, { useState } from react;import { Upload, Button, Toast } from douyinfe/semi-ui;function CompressUploadSolution() { const [fileList, setFileList] useState([]); // 图片压缩函数 const compressImage (file) { return new Promise((resolve) { // 检查文件类型 if (!file.type.startsWith(image/)) { resolve(file); return; } const reader new FileReader(); reader.readAsDataURL(file); reader.onload (e) { const img new Image(); img.src e.target.result; img.onload () { const canvas document.createElement(canvas); const ctx canvas.getContext(2d); // 设置压缩尺寸示例缩小到原始尺寸的50% const maxWidth img.width * 0.5; const maxHeight img.height * 0.5; canvas.width maxWidth; canvas.height maxHeight; // 绘制压缩后的图片 ctx.drawImage(img, 0, 0, maxWidth, maxHeight); // 转换为 Blob 对象 canvas.toBlob((blob) { // 创建新的 File 对象保留原始文件名 const compressedFile new File( [blob], file.name, { type: file.type, lastModified: Date.now() } ); // 添加自定义属性用于后续识别 compressedFile.compressed true; compressedFile.originalSize file.size; compressedFile.compressedSize blob.size; resolve(compressedFile); }, file.type, 0.7); // 压缩质量 0.7 }; }; reader.onerror () { resolve(file); // 出错时返回原始文件 }; }); }; // 自定义 beforeUpload 处理 const handleBeforeUpload async ({ file, fileList: currentFileList }) { try { // 对图片进行压缩 const compressedFile await compressImage(file); // 手动构建新的文件列表 const newFileList [...currentFileList]; // 查找并替换当前文件 const index newFileList.findIndex( (item) item.uid file.uid || item.name file.name ); if (index ! -1) { // 替换为压缩后的文件 newFileList[index] { ...compressedFile, uid: file.uid || Date.now(), // 保留或生成唯一标识 status: ready, // 设置状态为就绪 percent: 0 }; } else { // 如果是新文件添加到列表 newFileList.push({ ...compressedFile, uid: Date.now(), status: ready, percent: 0 }); } // 手动更新文件列表并触发 onChange setFileList(newFileList); // 返回压缩后的文件用于上传 return compressedFile; } catch (error) { console.error(文件压缩失败:, error); Toast.error(文件压缩失败使用原始文件上传); return file; } }; // 处理文件变化 const handleChange ({ fileList: newFileList, file }) { console.log(文件列表更新:, newFileList); setFileList(newFileList); // 这里可以添加业务逻辑比如显示压缩信息 if (file file.compressed) { const savings ((file.originalSize - file.compressedSize) / file.originalSize * 100).toFixed(1); Toast.success(文件已压缩节省了 ${savings}% 的空间); } }; return ( div style{{ padding: 20px }} Upload actionhttps://example.com/upload fileList{fileList} beforeUpload{handleBeforeUpload} onChange{handleChange} onSuccess{() { Toast.success(上传成功); }} onError{(error) { Toast.error(上传失败: ${error}); }} Button typeprimary选择图片自动压缩/Button /Upload div style{{ marginTop: 20px, color: #666 }} p当前文件数量: {fileList.length}/p p提示: 图片上传前会自动压缩请查看控制台输出/p /div /div );}export default CompressUploadSolution;### 3.2 关键点解析1.手动管理文件列表通过useState维护fileList状态并在beforeUpload中手动更新确保onChange能接收到变化。2.保留文件标识在压缩后创建新 File 对象时保留原始文件的uid或生成新的唯一标识避免文件列表混乱。3.状态管理同步在beforeUpload中手动设置status: ready和percent: 0确保文件状态正确。## 四、进阶技巧批量压缩与进度反馈对于需要处理多个文件的情况我们可以扩展解决方案添加批量压缩和进度反馈功能。javascriptimport React, { useState, useCallback } from react;import { Upload, Button, Progress, Toast } from douyinfe/semi-ui;function BatchCompressUpload() { const [fileList, setFileList] useState([]); const [compressProgress, setCompressProgress] useState(0); const [isCompressing, setIsCompressing] useState(false); // 批量压缩函数 const batchCompress useCallback(async (files) { setIsCompressing(true); const totalFiles files.length; let completedCount 0; const compressedFiles await Promise.all( files.map(async (file) { if (file.type.startsWith(image/)) { const compressed await compressImage(file); completedCount; setCompressProgress((completedCount / totalFiles) * 100); return compressed; } return file; }) ); setIsCompressing(false); setCompressProgress(100); return compressedFiles; }, []); const handleBeforeUpload async ({ file, fileList: currentFileList }) { // 这里可以添加批量处理逻辑 const compressedFile await compressImage(file); return compressedFile; }; return ( div {isCompressing ( Progress percent{compressProgress} / )} Upload actionhttps://example.com/upload fileList{fileList} beforeUpload{handleBeforeUpload} onChange{({ fileList: newFileList }) { setFileList(newFileList); }} multiple // 支持多文件上传 Button批量上传图片/Button /Upload /div );}## 五、常见问题与注意事项### 5.1 文件类型兼容性- 确保压缩函数处理非图片文件时正确返回原始文件- 对于 GIF 等动画格式压缩可能导致动画丢失建议特殊处理### 5.2 性能优化- 对于大文件可以考虑使用 Web Worker 进行压缩避免阻塞主线程- 设置合理的压缩参数平衡文件大小和图片质量### 5.3 错误处理- 在压缩失败时应回退到原始文件确保上传功能不受影响- 添加适当的用户反馈如 Toast 提示或进度条## 六、总结通过本文的分析和实践我们解决了 Semi Design Upload 组件在实现自定义压缩后onChange无法触发的问题。核心解决方案包括1.理解组件内部机制认识到beforeUpload返回新 File 对象会导致文件列表管理混乱。2.手动管理文件列表在beforeUpload中手动更新文件列表状态确保onChange能正常触发。3.保留文件标识与状态为压缩后的文件设置正确的uid、status和percent属性。4.提供完善的用户体验添加进度反馈、错误处理和压缩信息展示。这个解决方案不仅解决了技术问题还保持了代码的可维护性和扩展性。在实际项目中你可以根据具体需求调整压缩算法、添加更多文件类型支持或者集成更复杂的业务逻辑。记住理解组件的工作原理是解决这类问题的关键而手动管理状态则是确保事件正确触发的有效手段。
延伸阅读

更多相关文章

2026/9/6 6:59:56

如何让豆包生成word文档?AI导出鸭苹果版深度实测,附硬核QA

在移动办公日益普及的今天,越来越多的iPhone用户习惯用豆包处理工作、学习中的各类内容生成任务。但当一个包含复杂公式、代码块和多级标题的技术文档需要落地为Word文件时,许多用户发现:豆包虽然能生成内容,却无法直接输出一份排…

2026/9/9 18:37:53

GanttProject终极指南:5个简单步骤掌握免费开源项目管理工具

GanttProject终极指南:5个简单步骤掌握免费开源项目管理工具 【免费下载链接】ganttproject Official GanttProject repository. 项目地址: https://gitcode.com/gh_mirrors/ga/ganttproject 你是否曾经为复杂的项目管理感到头疼?面对繁多的任务、…

2026/9/10 17:18:49

智能任务拆解与提醒系统的设计与实现

1. 项目概述:智能任务拆解与提醒系统这个工具的核心价值在于帮我们解决一个职场常见痛点:面对复杂任务和紧迫截止日期时的手忙脚乱。想象一下,当你接到一个需要两周完成的年度报告,或是需要一个月筹备的大型活动方案时&#xff0c…

2026/9/10 17:18:49

分销商城系统开发平台推荐,低价系统为什么容易二次返工

今天给大家带来分销商城系统开发平台推荐,低价系统为什么容易二次返工。2026年6月,微信小店618大促数据显示,平台日均动销商家数同比增长95%,千万级成交额商品数量同比提升133%;“小店与卡包”入口成交额同比增长537%&…

2026/9/10 17:18:49

YOLOv8飞机蒙皮损伤检测实践:数据集构建、模型训练与部署

简介:面向计算机专业毕业设计打造的基于YOLOv8的飞机蒙皮损伤检测系统,是一套覆盖模型训练、评估、部署与可视化交互的完整目标检测方案。整套资源包含源码、可视化界面、完整数据集和部署说明,适合计科、人工智能、自动化、电子信息等专业学…

2026/9/10 17:18:49

二手交易系统Android实战:SQLite、Fragment与四大组件全流程

简介:在移动应用开发中,Android的组件化架构与本地数据持久化是构建稳定应用的基础。Activity负责界面生命周期管理,Fragment实现模块化布局切换,而SQLite则为离线业务提供轻量级数据存储方案。RecyclerView作为现代列表控件&…

2026/9/10 17:18:49

英语口语训练方法:从哑巴英语到流利交流

1. 项目概述"告别哑巴英语"这个标题直击中国英语学习者的痛点——学了十几年英语却开不了口的尴尬。作为一名在英语培训行业深耕十年的教师,我见过太多学员在应试教育体系下培养出的"哑巴英语"现象。他们可能拥有不错的词汇量和语法基础&#x…

2026/9/10 17:13:48

Chromium编译后桌面双图标问题:从现象到根治的排查指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/10 16:39:38

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/10 11:16:38

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/10 0:00:55

目录对比去重实战:用哈希算法精准清理重复文件

我电脑里现在还有一块换了三次机的“数据墓地”硬盘,里面存着2016年以前所有旧笔记本的完整备份。平时不觉得有什么,直到前阵子想把它整理归档,发现同一个安装包、同一批照片、同一份论文草稿,在几个不同的备份目录里反复出现。更…

2026/9/10 0:00:55

Leaflet离线地图完整Demo合集:内网部署与坐标纠偏实战

简介:这是一份面向Web GIS开发者的LeafLet离线地图示例合集,帮助开发者快速掌握离线地图从搭建到交互的完整流程。压缩包共723个文件,大小14.06MB,以319个js脚本、175个html页面和29个css样式文件为主体,配合png/svg图…

2026/9/10 0:00:55

MATLAB读取Rinex 3.02观测文件:多系统GNSS数据解析实战

简介:基于MATLAB开发的Rinex3.02版观测文件(o文件)读取代码包,面向卫星定位导航方向的学习者与研究人员,用于解决新版观测文件的数据解析、历元提取与时间转换问题。压缩包共4个文件,包含两个m脚本、一个19…

2026/9/10 12:32:02

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/10 15:19:50

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/10 15:49:53

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码