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

React上传Firebase前图片Resize报错:resize is not a function

客户端图片 resize 后上传 Firebase Storage 报错 'resize is not a function' 排查方案

问题概述

尝试在React客户端调整图片大小后上传至Firebase Storage,但控制台报错resize is not a function。已确认依赖配置正确,核心问题集中在图片resize环节,相关代码如下:

原React组件代码

import React, { useState, useEffect } from "react";
import Resizer from "react-image-file-resizer";
import { uploadImagesToFirebaseStorage } from "../firebase_handlers/firebaseStorageHandler";

const ImageFirestoreUplaoderComponent = () => {
  const [selectedFiles, setSelectedFiles] = useState([]);
  const [resizedImages, setResizedImages] = useState([]);
  const [thumbnailImages, setThumbnailImages] = useState([]);
  const [processingComplete, setProcessingComplete] = useState(false);

  // Function to encode a file to base64
  const encodeToBase64 = (file) =>
    new Promise((resolve, reject) => {
      const reader = new FileReader();

      reader.onload = () => {
        resolve(reader.result);
      };

      reader.onerror = (error) => {
        reject(error);
      };

      reader.readAsDataURL(file);
    });

  // Function to encode an array of files to base64
  const encodeFilesToBase64 = async (files) => {
    try {
      const encodedImages = await Promise.all(
        files.map((file) => encodeToBase64(file))
      );
      return encodedImages;
    } catch (error) {
      console.error("Error encoding files to base64:", error);
      return [];
    }
  };

  // Resizing and creating thumbnail miniature
  const resizeImages = async (base64Images) => {
    try {
      const resizedImages = [];
      const thumbnailImages = [];

      await Promise.all(
        base64Images.map(async (base64) => {
          try {
            // Resize image to a maximum width of 800px
            const resizedDataUrl = await Resizer.resize(base64, 800, 600);
            resizedImages.push({
              dataUrl: resizedDataUrl,
              file: null, // You can set the original file here if needed for later use
            });

            // Create a thumbnail miniature with a maximum width of 200px
            const thumbnailDataUrl = await Resizer.resize(base64, 200, 150);
            thumbnailImages.push({
              dataUrl: thumbnailDataUrl,
              file: null,
            });
          } catch (error) {
            console.error("Error resizing image:", error);
          }
        })
      );

      return { resizedImages, thumbnailImages };
    } catch (error) {
      console.error("Error resizing images:", error);
      return { resizedImages: [], thumbnailImages: [] };
    }
  };

  useEffect(() => {
    if (resizedImages.length > 0 && processingComplete) {
      // Call the upload function only if resizedImages contains valid data
      handleUploadImages();
      // Reset states after clicking upload
      setSelectedFiles([]);
      setResizedImages([]);
      setThumbnailImages([]);
      setProcessingComplete(false);
    }
  }, [resizedImages, processingComplete]);

  const handleImageChange = async (e) => {
    const files = e.target.files;
    await new Promise((resolve) => {
      setSelectedFiles((prevSelectedFiles) => [...prevSelectedFiles, ...files]);
      resolve();
    });

    const processImages = async () => {
      const base64Images = await encodeFilesToBase64(selectedFiles);
      const { resizedImages, thumbnailImages } = await resizeImages(
        base64Images
      );
      setResizedImages(resizedImages);
      setThumbnailImages(thumbnailImages);
      setProcessingComplete(true);
    };

    if (selectedFiles.length > 0) {
      await processImages();
    }
  };

  const handleUploadImages = async () => {
    await uploadImagesToFirebaseStorage(resizedImages, thumbnailImages);
  };

  return (
    <div>
      <input type="file" multiple onChange={handleImageChange} />
      <button onClick={handleUploadImages}>
        Upload Resized Images and Thumbnail Miniatures to Firebase
      </button>
    </div>
  );
};

export default ImageFirestoreUplaoderComponent;

原Firebase上传代码

import { storage } from "../firebase_setup/firebase";
import { ref, uploadString } from "firebase/storage";

const storageRef = ref(storage, "some-child");

const uploadImagesToFirebaseStorage = async (
  resizedImages,
  thumbnailImages
) => {
  try {
    const allImages = [...resizedImages, ...thumbnailImages];

    const uploadPromises = allImages.map(async (imageData, index) => {
      const { dataUrl } = imageData;

      // Convert dataUrl to base64 format (remove "data:image/jpeg;base64," or similar prefix)
      const base64Data = dataUrl.replace(/^data:image\/[a-z]+;base64,/, "");

      // Use uploadString to upload the base64 data to Firebase Storage
      const uploadTask = uploadString(storageRef, base64Data, "base64");
      console.log(imageData);
      return new Promise((resolve, reject) => {
        uploadTask.then(
          (snapshot) => {
            console.log(`Image ${index + 1} uploaded successfully!`);
            resolve(snapshot);
          },
          (error) => reject(error)
        );
      });
    });
    await Promise.all(uploadPromises);
    console.log("All images uploaded successfully!");
  } catch (error) {
    console.error("Error uploading images to Firebase Storage:", error);
  }
};

export { uploadImagesToFirebaseStorage };

错误原因分析

  1. react-image-file-resizer API 使用错误:该库的resize方法并非直接接收base64字符串和宽高参数,而是需要传入配置对象,且默认是回调式调用,不能直接用await。
  2. React State 异步更新问题:handleImageChange中调用setSelectedFiles后立即使用selectedFiles,此时state尚未更新,导致后续处理拿到的是空数组或旧数据。
  3. Firebase存储引用重复:固定的storageRef会导致所有上传的图片覆盖同一位置,无法保存多个文件。

修复方案及代码调整

1. 封装react-image-file-resizer的Promise版本

将回调式的resize方法封装为Promise,适配async/await语法:

const resizeImage = (fileOrBase64, maxWidth, maxHeight) => {
  return new Promise((resolve, reject) => {
    Resizer.imageFileResizer(
      fileOrBase64,
      maxWidth,
      maxHeight,
      "JPEG",
      80,
      0,
      (uri) => resolve(uri),
      "base64",
      maxWidth,
      maxHeight
    );
  });
};

2. 修正React组件中的resize逻辑和State处理

修改resizeImages和handleImageChange函数,解决API调用和异步State问题:

import React, { useState, useEffect } from "react";
import Resizer from "react-image-file-resizer";
import { uploadImagesToFirebaseStorage } from "../firebase_handlers/firebaseStorageHandler";

const ImageFirestoreUplaoderComponent = () => {
  const [selectedFiles, setSelectedFiles] = useState([]);
  const [resizedImages, setResizedImages] = useState([]);
  const [thumbnailImages, setThumbnailImages] = useState([]);
  const [processingComplete, setProcessingComplete] = useState(false);

  // 封装resize为Promise
  const resizeImage = (fileOrBase64, maxWidth, maxHeight) => {
    return new Promise((resolve, reject) => {
      Resizer.imageFileResizer(
        fileOrBase64,
        maxWidth,
        maxHeight,
        "JPEG",
        80,
        0,
        (uri) => resolve(uri),
        "base64",
        maxWidth,
        maxHeight
      );
    });
  };

  // Resizing and creating thumbnail miniature
  const resizeImages = async (files) => {
    try {
      const resizedImages = [];
      const thumbnailImages = [];

      await Promise.all(
        files.map(async (file) => {
          try {
            // 直接传入File对象,无需先转base64,库支持直接处理File
            const resizedDataUrl = await resizeImage(file, 800, 600);
            resizedImages.push({
              dataUrl: resizedDataUrl,
              file: file,
            });

            const thumbnailDataUrl = await resizeImage(file, 200, 150);
            thumbnailImages.push({
              dataUrl: thumbnailDataUrl,
              file: file,
            });
          } catch (error) {
            console.error("Error resizing image:", error);
          }
        })
      );

      return { resizedImages, thumbnailImages };
    } catch (error) {
      console.error("Error resizing images:", error);
      return { resizedImages: [], thumbnailImages: [] };
    }
  };

  useEffect(() => {
    if (resizedImages.length > 0 && processingComplete) {
      handleUploadImages();
      setSelectedFiles([]);
      setResizedImages([]);
      setThumbnailImages([]);
      setProcessingComplete(false);
    }
  }, [resizedImages, processingComplete]);

  const handleImageChange = async (e) => {
    const files = Array.from(e.target.files);
    setSelectedFiles(prev => [...prev, ...files]);

    // 直接使用刚获取的files,避免state异步问题
    const { resizedImages, thumbnailImages } = await resizeImages(files);
    setResizedImages(prev => [...prev, ...resizedImages]);
    setThumbnailImages(prev => [...prev, ...thumbnailImages]);
    setProcessingComplete(true);
  };

  const handleUploadImages = async () => {
    await uploadImagesToFirebaseStorage(resizedImages, thumbnailImages);
  };

  return (
    <div>
      <input type="file" multiple onChange={handleImageChange} />
      <button onClick={handleUploadImages}>
        Upload Resized Images and Thumbnail Miniatures to Firebase
      </button>
    </div>
  );
};

export default ImageFirestoreUplaoderComponent;

3. 修正Firebase上传代码的存储引用

为每个图片生成独立的存储路径,避免覆盖:

import { storage } from "../firebase_setup/firebase";
import { ref, uploadString } from "firebase/storage";

const uploadImagesToFirebaseStorage = async (
  resizedImages,
  thumbnailImages
) => {
  try {
    const allImages = [...resizedImages, ...thumbnailImages];

    const uploadPromises = allImages.map(async (imageData, index) => {
      const { dataUrl, file } = imageData;
      const base64Data = dataUrl.replace(/^data:image\/[a-z]+;base64,/, "");
      
      // 生成唯一的存储路径,用文件名+时间戳避免重复
      const fileName = `${Date.now()}_${file.name}`;
      const storageRef = ref(storage, `images/${fileName}`);

      const snapshot = await uploadString(storageRef, base64Data, "base64");
      console.log(`Image ${index + 1} uploaded successfully!`);
      return snapshot;
    });
    await Promise.all(uploadPromises);
    console.log("All images uploaded successfully!");
  } catch (error) {
    console.error("Error uploading images to Firebase Storage:", error);
  }
};

export { uploadImagesToFirebaseStorage };

关键修复点说明

  • 直接使用File对象传入resize方法,省去base64转换步骤,提升性能
  • 封装resize为Promise,适配async/await语法
  • 处理State异步更新问题,避免使用未更新的state值
  • 为每个上传文件生成唯一存储路径,防止文件覆盖

内容的提问来源于stack exchange,提问作者Jacor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 06:54:55