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

发布时间:2026/11/5 23:19:17

解决 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/11/1 16:08:10

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

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

2026/10/28 1:31:00

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

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

2026/11/5 16:51:33

EasyU优启通PE维护盘实战指南:制作、启动与核心操作全解析

1. 为什么一个PE维护盘值得单独拿出来聊装系统、修引导、救数据、清病毒,这些活儿但凡干过几次的人都知道,真正决定效率的往往不是你手速多快,而是手边那套PE环境靠不靠谱。EasyU(圈内习惯叫优启通)就是在这个场景下被…

2026/11/5 16:26:13

Rust原来很简单二:所有权系统

Part 2 核心篇 —— 所有权系统 本篇覆盖 第 6 ~ 10 章:所有权、借用与引用、生命周期、字符串与切片、内存模型。 ⚠️ 这是全书最重要的一篇。 把它吃透,Rust 从此“很简单”;跳过它,你会处处碰壁。请慢下来,动手敲每一段代码。 第 6 章 所有权:Rust 的第一性原理 6…

2026/11/5 15:00:49

基于Hadoop+Spark的民宿智能推荐系统设计与实现

1. 项目概述:大数据时代的民宿智能推荐这个毕业设计项目融合了当前大数据领域最主流的技术栈,构建了一个完整的民宿推荐系统解决方案。作为一名长期从事大数据系统开发的工程师,我认为这个选题非常贴合实际应用场景——随着旅游业的复苏和共享…

2026/11/5 14:10:30

ADS122U04IPWR+STM32F427ZI工业级温度测量信号链设计

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

2026/11/5 14:10:30

UART接口24位ADC搭配RS485,解决远距离温度监控难题

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

2026/11/5 14:10:30

UART接口24位ADC搭配PIC18F46K22的RTD与热电偶测温实践

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

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

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

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

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

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

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

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