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

React Firebase图片画廊应用图片重复上传两次问题排查

问题现象

React图片画廊应用其余功能运行正常,仅图片上传时会出现单张图片重复上传两次的问题,暂无法定位根因。

相关代码

图片上传逻辑useStorage自定义Hook

import { useState, useEffect } from "react";
import { fireStorage, fireStore } from "../firebase/firebase-config";
import { ref, getDownloadURL, uploadBytesResumable } from "firebase/storage";
import { collection, addDoc, serverTimestamp } from "firebase/firestore";

export default function useStorage(file) {
  const [progresspercent, setProgresspercent] = useState(0);
  const [error, setError] = useState(null);
  const [url, setUrl] = useState(null);
  const db = fireStore;

  useEffect(() => {
    const storageRef = ref(fireStorage, `Images/${file.name}`);
    const uploadTask = uploadBytesResumable(storageRef, file);


    uploadTask.on("state_changed",
      (snapshot) => {
        const progress =
          (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        setProgresspercent(progress);
      },
      (error) => {
        setError(error);
      },
      async () => {
        const imageUrl = await getDownloadURL(uploadTask.snapshot.ref).then((downloadUrl) => {
          addDoc(collection(db, 'Images'),{
            url: downloadUrl,
            createdAt: serverTimestamp()
          })
          setUrl(downloadUrl)
        });
      }
    );
  },[file]);

  return { progresspercent, url, error };
}

上传表单UploadForm组件

import React, { useState } from "react";
import ProgressBar from "./ProgressBar";

export default function UploadForm() {
  const [file, setFile] = useState(null);
  const [error, setError] = useState("");
  const allowedType = ["image/png", "image/jpg", "image/jpeg"];

  function changeHandler(e) {
    let selectedFile = e.target.files[0];

    if (selectedFile && allowedType.includes(selectedFile.type)) {
      setFile(selectedFile);
      setError("");
    } else {
      setFile(null);
      setError("Please select an image file");
    }
  }

  return (
    <form>
      <label>
        <input type="file" onChange={changeHandler} />
        <span>+</span>
      </label>
      <div className="output">
        {error && <div className="error">{error}</div>}
        {file && <div>{file.name}</div>}
        {file && <ProgressBar file={file} setFile={setFile} />}
      </div>
    </form>
  );
}

冗余Hook代码片段

(注:原提问中此处标注为useFirestore相关代码,实际贴出内容仍为useStorage实现,与前述上传逻辑代码几乎一致)

import { useState, useEffect } from "react";
import { fireStorage, firestore } from '../firebase-config'
import { ref, getDownloadURL, uploadBytesResumable } from "firebase/storage";
import { collection, addDoc, serverTimestamp } from "firebase/firestore";

export default function useStorage(file) {
  const [progresspercent, setProgresspercent] = useState(0);
  const [error, setError] = useState(null);
  const [url, setUrl] = useState(null);
  const db = firestore;

  useEffect(() => {
    const storageRef = ref(fireStorage, `Images/${file.name}`);
    const uploadTask = uploadBytesResumable(storageRef, file);


    uploadTask.on("state_changed",
      (snapshot) => {
        const progress =
          (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        setProgresspercent(progress);
      },
      (error) => {
        setError(error);
      },
      async () => {
        const imageUrl = await getDownloadURL(uploadTask.snapshot.ref).then((downloadUrl) => {
          addDoc(collection(db, 'Images'),{
            url: downloadUrl,
            createdAt: serverTimestamp()
          })
          setUrl(downloadUrl)
        });
      }
    );
  },[file]);

  return { progresspercent, url, error };
}
问题原因
  • 核心诱因是React 18开发环境默认启用严格模式(Strict Mode),会对未挂载组件的useEffect执行一次模拟挂载+卸载流程,用于排查无清理逻辑的副作用。当前useStorage中的上传副作用没有返回清理函数,第一次执行启动的上传任务不会被取消,第二次effect执行时又会启动一次相同的上传任务,最终触发两次上传。
  • 项目中存在两份几乎完全一致的useStorage实现,若存在重复调用Hook的情况,也会触发多次上传。
修复方案

删除项目中冗余的重复useStorage实现,统一使用单份上传逻辑;在useStorage的useEffect中增加空文件判断,同时返回清理函数,组件卸载或者effect重新执行前自动取消未完成的上传任务、解绑状态监听。修改后的useEffect代码如下:

useEffect(() => {
  if (!file) return;
  
  const storageRef = ref(fireStorage, `Images/${file.name}`);
  const uploadTask = uploadBytesResumable(storageRef, file);

  const unsubState = uploadTask.on("state_changed",
    (snapshot) => {
      const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
      setProgresspercent(progress);
    },
    (error) => {
      setError(error);
    },
    async () => {
      const downloadUrl = await getDownloadURL(uploadTask.snapshot.ref);
      await addDoc(collection(db, 'Images'),{
        url: downloadUrl,
        createdAt: serverTimestamp()
      })
      setUrl(downloadUrl)
    }
  );

  return () => {
    unsubState();
    uploadTask.cancel();
  }
},[file]);

修复后严格模式下第一次effect执行启动的上传任务会被清理函数主动取消,仅保留第二次执行的上传任务,不会再出现重复上传问题,且清理逻辑不会影响生产环境的正常上传流程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 03:06:17