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
相关产品推荐
相关产品推荐

