You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js中react-dropzone结合compressorjs实现图片压缩问题排查

解决react-dropzone结合compressorjs的图片压缩问题

问题根源

  1. 冗余的FileReader转换:compressorjs支持直接接收File/Blob对象作为输入,转成dataURL反而会触发压缩逻辑异常,这是图片未被压缩的核心原因。
  2. 状态逻辑混乱:原代码先把未压缩的文件加入状态,压缩成功后又追加压缩文件,导致状态中同时存在原文件和压缩文件,未实现替换逻辑。
  3. 重复文件检查逻辑错误:嵌套循环效率低下,且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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 08:55:37