Next.js中react-dropzone结合compressorjs实现图片压缩问题排查
解决react-dropzone结合compressorjs的图片压缩问题
问题根源
- 冗余的FileReader转换:compressorjs支持直接接收
File/Blob对象作为输入,转成dataURL反而会触发压缩逻辑异常,这是图片未被压缩的核心原因。 - 状态逻辑混乱:原代码先把未压缩的文件加入状态,压缩成功后又追加压缩文件,导致状态中同时存在原文件和压缩文件,未实现替换逻辑。
- 重复文件检查逻辑错误:嵌套循环效率低下,且
setFiles的用法不符合React状态更新规范(仅接受新状态或状态更新函数)。
修正后的完整代码
import { useState, useEffect } from 'react'; import { useDropzone } from 'react-dropzone'; import Compressor from 'compressorjs'; const ImageUploader = () => { const [files, setFiles] = useState([]); const [text, setText] = useState("Drag and Drop or click here to upload Images"); const { getRootProps, getInputProps } = useDropzone({ accept: { 'image/*': [] }, onDrop: (acceptedFiles) => { // 先过滤已存在的重复文件 const uniqueFiles = acceptedFiles.filter(newFile => !files.some(existing => existing.name === newFile.name) ); // 检查总数量是否超出上限 if (files.length + uniqueFiles.length > 9) { alert("最多只能选择9张图片"); return; } // 对每个文件执行压缩 uniqueFiles.forEach(rawFile => { new Compressor(rawFile, { quality: 0.8, // 0-1之间调整压缩质量 success: (compressedFile) => { // 给压缩后的文件添加预览URL const processedFile = { ...compressedFile, preview: URL.createObjectURL(compressedFile) }; setFiles(prev => [...prev, processedFile]); }, error: (err) => { console.error('图片压缩失败:', err); // 压缩失败时回退添加原文件 const fallbackFile = { ...rawFile, preview: URL.createObjectURL(rawFile) }; setFiles(prev => [...prev, fallbackFile]); } }); }); // 提示重复文件 const duplicates = acceptedFiles.filter(newFile => files.some(existing => existing.name === newFile.name) ); if (duplicates.length > 0) { alert(`${duplicates.map(f => f.name).join(', ')} 是重复文件`); } } }); const removeFile = (targetFile) => () => { setFiles(prev => prev.filter(file => file !== targetFile)); // 立即释放预览URL,避免内存泄漏 URL.revokeObjectURL(targetFile.preview); // 更新提示文本 if (files.length - 1 === 0) { setText("Drag and Drop or click here to upload Images"); } }; useEffect(() => { // 组件卸载时清理所有预览URL return () => { files.forEach(file => URL.revokeObjectURL(file.preview)); }; }, [files]); return ( <div {...getRootProps()}> <input {...getInputProps()} /> <p>{text}</p> <div> {files.map((file, idx) => ( <div key={idx} style={{ display: 'inline-block', margin: '0 8px 8px 0' }}> <img src={file.preview} alt={file.name} style={{ width: '100px', height: '100px', objectFit: 'cover' }} /> <button onClick={removeFile(file)} style={{ display: 'block', marginTop: '4px' }}>移除</button> </div> ))} </div> </div> ); }; export default ImageUploader;
关键修改说明
- 直接传入File对象给Compressor:去掉冗余的FileReader转换,确保压缩逻辑正常触发。
- 先过滤重复文件:避免重复文件进入压缩流程,同时精准提示重复项。
- 状态仅保留压缩后文件:压缩成功后再将处理后的文件加入状态,替换原有的直接添加未压缩文件逻辑。
- 规范setFiles用法:始终使用函数式更新
setFiles(prev => ...),确保基于最新状态更新。 - 优化内存泄漏处理:移除文件时立即释放对应预览URL,而非仅在组件卸载时处理。
- 增加错误回退:压缩失败时自动添加原文件并打印错误日志,提升用户体验。
内容的提问来源于stack exchange,提问作者Mohammed Bekele
相关产品推荐
相关产品推荐

